Đăng ký Đăng nhập
Trang chủ Chuyên đề vét cạn thử sai ...

Tài liệu Chuyên đề vét cạn thử sai

.DOC
28
607
82

Mô tả:

CHUYÊN ĐỀ: PHƯƠNG PHÁP THỬ SAI (Duyệt - Vét cạn). 1. Bài toán về phương pháp thử - sai: Trong một số bài toán việc tìm nghiệm có thể qui về việc tìm vector hữu hạn (x1,x2,…,xn). Trong đó n có thể xác định trước hoặc không xác định trước. Vector cần tìm phải thỏa mãn điều kiện nào đó tùy thuộc vào từng bài toán cụ thể. Trong đó các thành phần xi được chọn ra từ tập Ai. Phương pháp: - Lần lượt liệt kê tất cả các khả năng của nghiệm (Gọi là phần thử - duyệt). - Với mỗi vector nghiệm ta kiểm tra xem nghiệm đó của thỏa mãn điều kiện của đầu bài hay không,…. (Phần sai) Ví dụ 1: Cho dãy số nguyên a1,a2,…,an ( ai <=109) và một số nguyên dương S (S<=109), thực hiện các yêu cầu sau: a) Đếm số lượng dãy con liên tiếp của dãy A có số phần tử lớn hơn 3 và có tổng bằng S b) Chọn 3 phần tử trong dãy mà tích của ba phần tử là lớn nhất. c) Liệt kê tất cả các cách chọn một tập gồm các số trong dãy để tổng các số trong tập được chọn bằng S Phân tích ví dụ: a) Vector nghiệm là (i,j) (Trong đó 1<=i Chi phí duyệt O(n3); Chi phí kiểm tra là O(1) => Độ phức tạp của thuật toán là O(n3) c) Vector nghiệm là (x1,x2,…,xn). Trong đó xi=1 nếu ai được chọn và ngược lại. Ta phải dùng đệ qui để duyệt các dãy nhị phân độ dài n, với mỗi dãy nhị phân ta tính tổng và kiểm tra S. *. Nhận xét: Ta thấy rằng độ phức tạp của 3 thuật toán trên là quá lớn: - Thuật toán của ý a và b là O(n3) chỉ giải quyết được với n<=400 - Thuật toán của ý c là thuật toán đệ qui giải quyết được với n<=20 Vậy có cách tiếp cận nào để giải quyết bài toán: 2. Giải bài toán bằng chiến lược thử sai: - Đoán nhận vector nghiệm: Phải xác định nghiệm gồm bao nhiêu thành phần, miền giá trị của từng thành phần và các rằng buộc giữa các thành phần. Đây là công việc quan trọng mang ý nghĩa quyết định đến việc giải quyết bài toán. - Viết thuật toán, mịn dần thuật toán. - Tối ưu thuật toán: Ta có thể tối ưu thuật toán bằng cách giảm thiểu số trường hợp phải thử (Giảm thiểu thủ tục Thử) hoặc ta có thể giảm chi phí phần kiểm tra (Giảm phần Sai) hoặc ta có thể giảm cả 2 phần. 3. Bài tập áp dụng: Bài 1a) Cho dãy số nguyên a1,a2,…,an ( ai <=109; n<=105) và một số nguyên dương S (S<=109). Đếm số lượng dãy con liên tiếp của dãy a1,a2,…,an có số phần tử lớn hơn 3 và có tổng bằng S. *) Thuật toán 1: Function Var kiemtra(i,j:longint):Boolean; sum:int64; k:longint; Begin Sum:=0; For k:= i to j do sum:=sum+a[k]; Exit(sum=S); End; **************** Procedure try; Var i,j:longint; Begin Count:=0; For i:= 1 to n -3 do For j:= i+3 to n do do If kiemtra(i,j) then inc(count); End; Nhận xét: Với thuật toán trên thì độ phức tạp là O(n3) nên chỉ chạy được với n<=400. *). Thuật toán 2: Cài tiến việc kiểm tra từ O(n) về O(1). - Ta tính toán tổng tất cả các phần tử a[i] đến a[j]. Như vậy khi kiểm tra chỉ việc lấy ra chứ không phải tính toán nữa. Gọi mảng L là mảng một chiều mà L[i] là tổng các phần tử từ phần tử 1 đến phần tử thứ i. Procedure chuanbi; Var i:longint; Begin Fillchar(L,sizeof(L),0); For i:= 1 to n do L[i]:=L[i-1]+a[i]; End: **************** Function Begin kiemtra(i,j:longint):Boolean; Exit(L[j]-L[i-1]=S); End; **************** Procedure try; Var i,j:longint; Begin Count:=0; For i:= 1 to n -3 do For j:= i+3 to n do do If kiemtra(i,j) then inc(count); End; Nhận xét: - Với thuật toán trên do ta tính trước được tổng cấc phần từ tử a[i] đến a[j] nên với mỗi lần duyệt ở thủ tục Try thì ta chỉ cần lấy kết quả ra để kiểm tra => Độ phức tạp của thuật toán là O(n2). Với độ phức tạp này ta có thể chạy được với n<=5000. (Ta cũng có thể làm bằng cách tính tổng cộng dồn sau mỗi khi tăng j) *. Thuật toán 3: Ta tối ưu thuật toán ở phần thử (Phần duyệt). - Với mỗi phần tử thứ i: Ta tìm kiếm xem có bao nhiêu chỉ số j mà (j>i+3, L[i-1] + S =L[j]) - Việc tìm j thỏa mãn điều kiện trên ta có thể tìm bằng thuật toán tìm kiếm nhị phân với độ phức tạp là O(log n). - Để tìm kiếm được ta phải sắp xếp tăng dần mảng Q (Chính là mảng L nhưng có thêm phần chỉ số). Cụ thể ta làm như sau: - Tính mảng L - Độ phức tạp O(n). - Tính mảng Q. For i:= 1 to n do Begin Q[i].GT:=L[i]; Q[i].CS:=i; End; - Sắp xếp (Quicksort) mảng Q theo trường GT - Độ Phức tạp O(nlogn) - Duyệt: Với mỗi i ta tìm Q[j] mà Q[j].GT = S + L[i-1] và Q[i].CS>i+3. Như vậy: Với thuật toán này thì phần duyệt có độ phức tạp là O(n) còn phẩn kiemtra có chi phí là O(logn) => Toàn thuật toán có chi phí là O(nlogn) => Có thể chạy được với n<=105). Muốn viết được thuật toán trên ta phải viết được thuật toán tìm kiếm nhị phân và thuật sắp xếp quicksort: *. Thuật toán tìm kiếm nhị phân: Function Nhiphan(L,H:Longint):Longint; Var Dau,Cuoi,Giua;Longint; Begin Dau:=L; Cuoi:=H; Giua:=(Dau+Cuoi) Div 2; While (a[Giua]<>K ) and (Dau<=Cuoi) do Begin If A[giua]>K then Cuoi:=giua-1 Else Dau:=Giua+1; Giua:=(Dau+Cuoi) Div 2; End; If a[giua]=K then Exit(Giua) else Exit (0); End; *. Thuật toán sắp xếp nhanh (Quicksort). Procedure Quicksort(L,H:Longint); Var i,j:Longint; x,tg:Longint; Begin i:=L; j:=H; x:=a[(L+H) Div 2]; Repeat While a[i].keyx.key do dec(j); If i<=j then Begin Đỗi chỗ a[i] và a[j] Inc(i); Dec(j); End; Until i>j; If Ly.gt then Exit (1) Else Exit(-1); End; Procedure Quicksort(L,H:Longint); Var i,j:Longint; x,tg:Longint; Begin i:=L; j:=H; x:=a[(L+H) Div 2]; Repeat While a[i].keyx.key do dec(j); If i<=j then Begin Đỗi chỗ a[i] và a[j] Inc(i); Dec(j); End; Until i>j; If Li+3. Như vậy: Với thuật toán này thì phần duyệt có độ phức tạp là O(n) còn phẩn kiemtra có chi phí là O(logn) => Toàn thuật toán có chi phí là O(nlogn) => Cos thể chạy được với n<=105). Kết luận: Phương pháp Thử - Sai thực chất là duyệt tất cả các khả năng có thể là nghiệm của bài toán: - Phần duyệt: Có thể duyệt bằng các trường hợp sau: +. Dùng vòng lặp. + Sinh tuần tự các véc tơ. +. Dùng đệ qui,… - Phần kiểm tra. HỆ THỐNG CÁC BÀI TẬP VỀ DUYỆT Bài 1: ESEQ Cho dãy số nguyên A gồm N phần tử A1, A2, .., AN, tìm số cặp chỉ số i, j thỏa mãn: i  p 1 Ap  N  q j Aq với 1 ≤ i < j ≤ N Dữ liệu vào trong file “ESEQ.INP” có dạng: - Dòng đầu là số nguyên dương N (2 ≤ N ≤ 105) - Dòng tiếp theo chứa N số nguyên A1, A2, .., AN (|Ai|<109), các số cách nhau một dấu cách. Kết quả ra file “ESEQ.OUT” có dạng: gồm một số là số cặp tìm được. ESEQ.INP 3 101 ESEQ.OUT 3 Ý tưởng: - Duyệt i,j: Với i là phần tử cuối cùng của đoạn 1 và j là phần tử đầu tiên của đoạn 2. - Với mỗi i,j ta tìm tổng của đoạn 1 va tổng của đoạn 2. mất O(n). => Độ phức tạp của cả thuật toán là O(n3). Mã code như sau: For i:=1 to n-1 do For j:=i+1 to n do Begin S1:=0; For k:=1 to i do S1:=S1+a[k]; S2:=0; For k:=j to n do S2:=S2+a[k]; If S1=S2 then inc(kq); End; Cải tiến 1: ta cải tiến phần kiểm tra từ O(n) về O(1) Bằng cách tính trước mảng cộng dồn. - Gọi mảng L. Trong đó L[i] là tổng các phần tử từ phần tử 1 đến phần từ i. - Gọi mảng R. Trong đó L[j] là tổng các phần tử từ phần tử j đến phần từ n. Thuật toán trên trở thành. For i:=1 to n-1 do For j:=i+1 to n do Begin If L[i]=R[j] then inc(kq); End; Cải tiến 2: Cải tiến phần duyệt. Với mỗi i Ta tìm kiếm nhị phân trên mảng R=L[i]. Để đếm được có bao nhiêu R[j]=L[i] ta phải sắp xếp mảng R theo giá trị đồng thời cũng phải lưu chỉ số ban đầu để kiểm tra thỏa mãn điều kiện (ix do Dec(j); if i<=j then Begin tg:=sp[i]; sp[i]:=sp[j]; sp[j]:=tg; inc(i); dec(j); end; until i>j; if il then quicksort(l,j); end; Function Lan(k,l:longint):longint; Var tg,l1:longint; Begin tg:=0; l1:=l; While (l1>0) and (sp[l1].gt=st[k]) do Begin if sp[l1].cs>k then tg:=tg+1; Dec(l1); end; l1:=l+1; While (l1<=n) and (sp[l1].gt=st[k]) do Begin if sp[l1].cs>k then tg:=tg+1; inc(l1); end; Exit(tg); end; Function tknp(k:longint):longint; Var dau,cuoi,giua:longint; Begin dau:=1; cuoi:=n; while (dau<=cuoi) do Begin giua:=(dau+cuoi) Div 2; if sp[giua].gt=st[k] then exit(giua); if sp[giua].gt0 then Kq:=kq+Lan(i,tg); end; end; Procedure ghikq; Var f:text; Begin Assign(f,fo); rewrite(f); Write(f,kq); Close(f); end; BEGIN Docdl; quicksort(1,n); Xuli; Ghikq; END. Lưu ý: - Phải có hàm Lan sang hai bên vì điều kiện đầu bài ix do dec(j); if i<=j then Begin tg:=a[i]; a[i]:=a[j]; a[j]:=tg; inc(i); Dec(j); end; until i>j; if il then quicksort(l,j); end; Function lan(l,k:longint):longint; Var kq1:longint; l1:longint; Begin if k=0 then exit(0); kq1:=0; l1:=k; While (l1>l) and (a[l1]=a[k]) do Begin inc(kq1); dec(l1); end; l1:=K+1; While (l1<=n) and (a[l1]=a[k]) do Begin inc(kq1); inc(l1); end; Exit(kq1); end; Function tknp(l,h,x:longint):longint; Var dau,cuoi,giua:longint; Begin Dau:=l; cuoi:=h; While dau<=cuoi do Begin giua:=(dau+cuoi) div 2; if a[giua]=x then exit(giua); if a[giua]0 then kq:=kq+lan(i,kq1); end; end; procedure ghikq; Var f:text; Begin Assign(f,fo); rewrite(f); Write(f,kq); Close(f); end; BEGIN Docdl; Quicksort(1,n); Xuli; Ghikq; END. LẬT XU Có 16 đồồng xu xếếp thành bảng 4x4, mồỗi đồồng xu có th ể úp hoặc ngửa như hình vẽỗ sau: Mầồu đẽn thể hiện đồồng xu úp, màu trắếng thể hiện đồồng xu ngửa. Tại mồỗi bước ta có phép biếến đổi sau: Chọn một đồồng xu và thay đổi trạng thái của ồ đó và tầết c ả các ồ chung cạnh (úp thành ngửa, ngửa thành úp). Cho trước một trạng thái các đồồng xu, hãy l ập trình tm sồế phép biếến đổi ít nhầết để đưa vếồ trạng thái tầết cả các đồồng xu hoặc đếồu úp hoặc đếồu ngửa. Input  Gồồm 4 dòng, mồỗi dòng 4 ký tự ‘w’ - mồ tả trạng thái ngửa hoặc ‘b’- mồ tả trạng thái úp. Output  Nếếu có thể biếến đổi được ghi sồế phép biếến đổi ít nhầết nếếu khồng ghi -1 latxu.inp bwbw wwww bbwb bwwb latxu.out -1 latxu.inp bwwb bbwb bwwb bwww const fi='Latxu.inp'; fo='Latxu.out'; Type mang=array[0..5,0..5] of longint; Var A,x:mang; kq:longint; Procedure docdl; Var i,j:longint; ch:char; f:text; Begin assign(f,fi); Reset(f); Fillchar(a,sizeof(a),0); For i:=1 to 4 do Begin For j:=1 to 4 do Begin Read(f,ch); if ch='b' then a[i,j]:=0 else a[i,j]:=1; end; readln(f); end; Close(f); end; Function kiemtra(B:mang):boolean; Var i,j,dem:longint; Begin For i:=1 to 4 do latxu.out 4 For j:=1 to 4 do if x[i,j]=1 then Begin B[i,j]:=(B[i,j]+1) mod 2; B[i-1,j]:=(B[i-1,j]+1) mod 2; B[i+1,j]:=(B[i+1,j] +1) mod 2; B[i,j-1]:=(B[i,j-1]+1) mod 2; B[i,j+1]:=(B[i,j+1]+1) mod 2; end; dem:=0; For i:=1 to 4 do For j:=1 to 4 do if B[i,j]=0 then inc(dem); Exit((dem=0) or (dem=16)); End; Function dem:longint; Var i,j,s:longint; Begin s:=0; For i:=1 to 4 do For j:=1 to 4 do if x[i,j]=1 then inc(s); exit(s); end; Procedure xulikq; var i,j:longint; Begin if kiemtra(A) then if dem Ta dùng hai biên chỉ số i, j chạy từ hai đầu về. Chương trình mẫu: {$M 64000000,0} {$MODE OBJFBC} {$INLINE ON} {$H-,I+,Q+,R+,S+} Const InputFile = 'PSEQ.INP'; OutputFile = 'PSEQ.OUT'; max = 15000; Type Arr1Int = array[1..max] of Integer; Var a : Arr1Int; n, m, First, kk : Integer; { ======================================================== } Procedure Enter; Var fi : text; i : Integer; Begin Assign(fi, InputFile); Reset(fi); Read(fi, n, m); For i := 1 to n do Read(fi, a[i]); Close(fi); End; { ================================================== } Procedure Swap(Var x, y : Integer); Var Temp : Integer; Begin Temp := x; x := y; y := Temp; End; { =============================================== } Procedure Sort(L, H : Integer); Var i, j, key : Integer; Begin If L >= H then Exit; i := L; j := H; key := a[L + Random(H - L + 1)]; Repeat While a[i] < key do Inc(i); While a[j] > key do Dec(j); If i <= j then Begin If i < j then Swap(a[i], a[j]); Inc(i); Dec(j); End; Until i > j; Sort(L, j); Sort(i, H); End; { ========================================= } Procedure Test; Var i : Integer; Begin First := 1; For i := n downto 1 do If i <= First then Break Else If a[i] + a[First] <= m then Begin Inc(kk); Inc(First); End Else If a[i] <= m then Inc(kk) Else Begin kk := 0; Break; End; End; { =================================================== } Procedure Process; Begin kk := 0; If n = 1 then Begin If a[n] <= m then kk := 1; End Else Test; End; { ========================================================= } Procedure PrintResult; Var fo : text; Begin Assign(fo, OutputFile); Rewrite(fo); Writeln(fo, kk); Close(fo); End; { =========================================================== } Begin Enter; Sort(1, n); Process; PrintResult; End. COUNTING Cho đoạn số nguyên [a, b] 0 - Xem thêm -

Tài liệu liên quan