Phân tích giải thuật bài toán phân tích số
Các bài toán phân tích sốCao Minh AnhTrong quá trình học chắc các bạn sẽ gặp rất nhiều các bài toán về phân tích số. Các bài này có rất nhiều dạng khác nhau. Sau đây tôi xin đề cập đến một số bài toán để cùng trao đổi với các bạn.Bài 1: Cho số nguyên dương M(M<=232),tìm cách phân tích M ra các số nguyên dương khác nhau có tổng bằng M sao cho tích của chúng là lớn nhất.Mart.inp MMart.outa1 a2 a3 … ak Ví dụ: Mart.inp11Mart.out2 4 5Với bài toán trên thì có nhiều cách giải khác nhau,sau đây tôi xin trình bày với các bạn một thuật toán để giải bài này có tính mô phỏng. Đó là “thuật toán rải đậu”.Sau đây tôi xin trình bầy tư tưởng thuật toán như sau:Thuật toán:+Xây dựng mảng a với a[1]=2;a[I]=a[I-1]+1;(I>=2)Tìm k lớn nhất sau cho a[1]+a[2]+ +a[k]<=M;+ Xét d:=M-(a[1]+a[2]+ +a[k]); + Bây giờ ta tưởng tượng ta đang có d hạt đậu, chúng ta bắt đầu rải từng hạt đậu vào các a[I](1<=I<=k) theo chiều từ k1. Nếu rải đến 1 mà vẫn còn thì ta vẫn rải lại bắt đầu từ k lại,cứ như thế cho đến khi không còn hạt đậu nào. Khi rải đậu đến ô nào thì tăng giá trị của ô đó lên. Sau đó xuất mảng a[i] ra là giải quyết xong bài toán.Xong trên chỉ là tư tưởng của thuật toán trên, nếu làm theo cách đó thì ta sẽ tốn nhiều thời gian và dữ liệu. Sau đây tôi xin giới thiệu một cách cài đặt giúp tăng thời gian và giảm dử liệu:Ở bước một ta tìm K lớn nhất cho a[1]+a[2]+ +a[k]<=M+ Giải phương trình:2+3+4+ +(k-1)<=M<-> k(k-1)/2 <=M-1.Giải phương trình trên tìm nghiệm k nguyên trong khoảng nghiệm là được.Bước hai: Thay vì mỗi lần ta rãi 1 hạt, mà ta rải (d div k) hạt đậu từ a[k]àa[1] (Với d là số hạt đậu còn lại, k là số ô để ta bỏ đậu vào), sau đó sẽ còn dư (d mod k) ta lại rải lại từ cuối đến đầu. Như vậy chỉ mất 2 lần rải. Một điểm nữa là kết quả xuất ra là một dãy liên tiếp đức đoạn, có nghĩa là nó mất một số trên đoạn tăng đó.Ví dụ: 22 à 2 3 4 6 7 (Mất 5)40 à 2 3 5 6 7 8 9 (Mất 4)Như vậy ta cũng không cần mảng để lưu nữa, ta sẽ tính xem những số từ đâu đến đâu tăng M div n,những số từ đâu đến đâu tăng (M div n) + 1.Sau đây là bài giải hoàn chỉnh:uses crt;const fi='RAIDAU.inp';go='RAIDAU.out';var m,r,sp,cs,k,du,delta :longint;k1,k2 :real; f,g :text;procedure openf;beginassign(f,fi);reset(f);assign(g,go);rewrite(g);end;procedure closef;beginclose(f);close(g);end;procedure Init;beginreadln(f,m);end;procedure Timk;begindelta:=1+8*(M+1);k1:=((-1)-sqrt(delta))/2;k2:=((-1)+sqrt(delta))/2;k:=trunc(k2); du:=M+1-(k*(k+1) div 2);end;procedure Print;var i:longint;begink:=k-1;cs:=du div k;sp:=du mod k;r:=2;for i:=1 to k-sp dobeginwrite(g,r+cs,' ');r:=r+1;if i mod 100=0 then writeln(g);end;for i:=k-sp+1 to k dobeginwrite(g,r+cs+1,' ');r:=r+1;if i mod 100=0 then writeln(g);end;end;Begin Openf;Init;Timk;Print;Closef;End.Với cách làm trên ta có thể xử lý rất nhanh,vừa đỡ dữ liệu.Sau đây là một bài khác.Bài 2: Phân tích số đòi hỏi đưa ra tất cả các cách có thể có.+Với những bài như trên thì dữ liệu vào sẽ nhỏ,và phương pháp đơn giản nhất là duyệt.Ví dụ: Cho một số n.Hãy đưa ra tất cả các cách phân tích n ra các số nguyên tố có thể giống nhau sao cho tổng của chúng bằng n.Với n=11Có 6 cách la:2 2 2 2 32 2 2 52 2 72 3 3 33 3 511 function Nto(n:longint):boolean;var i:integer;beginNto:=false;For i:=2 to round(sqrt(n)) do If n mod i=0 then exit;Nto:=true;End;Procedure Print;Var i:integer;BeginFor i:=1 to dem do write(g,a[i]:4);Writeln(g);End;Procedure Phantich(n,k,d:integer);BeginA[d]:=k;If n=0 thenBeginDem:=d;Print;EndElse BeginFor i:=k to n doIf nto(i) then phantich(n-i,i,d+1);N:=n+k;End; End;Từ bài trên có thể chuyển qua rất nhiều bài như sau:+Phân tích n ra các số nguyên tố khác nhau.Chỉ cần sửa một chút là:BeginFor i:=k+1 to n doIf nto(i) then phantich(n-i,i,d+1);N:=n+k;End;+Nếu bài toán là phân tích ra các số có thể giống nhau thì ta sửa như sau:BeginFor i:=k to n do phantich(n-i,i,d+1);N:=n+k;End;Còn nếu phân tích ra các số khác nhau để tổng bằng n thì sửa như sau:BeginFor i:=k+1 to n do phantich(n-i,i,d+1);N:=n+k;End;Với những bài phân tích ra tất cả các cách thì rất đơn giản.Nhưng với các bài toán chỉ yêu cầu đưa ra số cách có thể với n lớn mà đệ quy như trên không thể làm được thì rất khó.Với những bài như thế thì làm bằng phương pháp Quy Hoạch Động rất đơn giản và nhanh chóng.Bài 3:+Dạng 1:Các số hạng có thể giống nhau. Bài toán: Cho một số n<=2000 hãy tìm số cách phân tích n thành các số nguyên tố có thể giống nhau sau cho tổng của chúng bằng n.Phantich.inp phantich.out N S(Số cách)Ví dụ;N=11 ---------à S=6.Phương pháp :F[n]=Tổng các F[n-p] với p:là số nguyên tố.Và F[n-p] là số cách phân tích n ra các số nguyên tố nhỏ hơn hoặc bằng p.Khởi tạo: Fx[i]=1 nếu i là số nguyên tố.Khi xét các số nguyên tố p từ 2 n thì ta luôn có:Fx[j]:=Fx[j]+Fx[j-p];for i:=2 to n doif nto(i) thenbeginFx[i]:=Fx[i]+1;for j:=i+2 to n doFx[j]:=Fx[j]+Fx[j-i];end;Đây là chương trình chính:uses crt;const fi='Prime.inp';go='Prime.out';type arr=array[1 50] of byte; var n,k,dem :longint;Fx :array[0 2000] of ^arr;Donvi :arr;p :pointer;f,g :text;procedure Openf;beginassign(f,fi);reset(f);assign(g,go);rewrite(g);end;procedure Closef;beginclose(f);close(g);end;procedure Input;beginreadln(f,n);donvi[1]:=1;end;function nto(p:longint):boolean; var k:longint;beginNto:=false;for k:=2 to round(sqrt(p)) doif p mod k=0 then exit;Nto:=true;end;procedure Print;var i:integer;beginfor i:=k downto 1 do write(g,fx[n]^[i]);end;procedure Cong(a,b:arr;var c:arr);var i,du :integer;begini:=0;du:=0;while i<> begininc(i);c[i] :=(a[i]+b[i]+du) mod 10;du :=(a[i]+b[i]+du) div 10;end; [...]... do Cong(Fx[i+h]^,Fx[i]^,Fx[i+h]^); end; end; begin clrscr; Mark(p); openf; Input; Main; Print; release(p); Bài tốn: Cho một số n<=2000 hãy tìm số cách phân tích n thành các số ngun tố có thể giống nhau sau cho tổng của chúng bằng n. Phantich.inp phantich.out N S (Số cách) Ví dụ; N=11 à S=6. Phương pháp : F[n]=Tổng các F[n-p] với p:là số nguyên tố.Và F[n-p] là số cách phân tích n ra các số nguyên tố nhỏ hơn hoặc bằng p. Khởi tạo: Fx[i]=1 nếu i là số nguyên tố. Khi xét các số nguyên tố p... Timk; begin delta:=1+8*(M+1); k1:=((-1)-sqrt(delta))/2; k2:=((-1)+sqrt(delta))/2; k:=trunc(k2); end; begin clrscr; Mark(p); openf; Input; Main; Print; release(p); Closef; end. + Bài trên cịn có thể áp dụng với phân tích n ra các số khơng cần ngun tố có thể giống nhau sao cho tổng các số bằng n.Yêu cầu đưa ra số cách. Chỉ sửa lại một chút như sau; Procedure Main; var i,j:integer; begin for i:=1 to n do begin Fx[i]:=Fx[i]+1; for j:=i+1 to n do Fx[j]:=Fx[j]+Fx[j-i]; end; end; ... then begin for h:=2 to n-i do if nto(h) then Fx[i+h]:=Fx[i+h]+Fx[i]; end; end; begin clrscr; openf; Input; Main; Print; Closef; end. Bài này các bạn chỉ cần làm thêm chương trình cộng sơ lớn là xong.Tuy nhiên vẫn cịn 1 dạng nữa mình vẫn chưa làm được đó là:Tính số cách phân tích số n ra các số (hay ngun tố ) khác nhau. Ví dụ: N=5 có 3 cách là: ...Gọi Fx[i] là số cách tạo thành i; Khởi tạo:Fx[i]=1 với 1<=i<=n. Vì lặp lại vẫn được nên ta có cơng thức sau: For h:=1 to n-i do Fx[i+h]:=Fx[i+h]+Fx[i]; Tổng quan như sau: For i:=1 to n do fx[i]:=1; For i:=1 to n do Begin For h:=1 to n-i do Fx[i+h]:=Fx[i+h]+fx[i]; End; Chỉ chạy h đến n-i để khỏi phải tính những cách tạo thành số lớn hơn n. Đây là chương trình chính: uses . Các bài toán phân tích sốCao Minh AnhTrong quá trình học chắc các bạn sẽ gặp rất nhiều các bài toán về phân tích số. Các bài này có rất nhiều. các bạn một thuật toán để giải bài này có tính mô phỏng. Đó là thuật toán rải đậu”.Sau đây tôi xin trình bầy tư tưởng thuật toán như sau :Thuật toán: +Xây