Các bài toán phân tích số
Cao Minh Anh
Trong 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<=2
32
),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
M
Mart.out
a1 a2 a3 … ak
Ví dụ:
Mart.inp
11
Mart.out
2 4 5
Vớ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;
begin
assign(f,fi);
reset(f);
assign(g,go);
rewrite(g);
end;
procedure closef;
begin
close(f);
close(g);
end;
procedure Init;
begin
readln(f,m);
end;
procedure Timk;
begin
delta:=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;
begin
k:=k-1;
cs:=du div k;
sp:=du mod k;
r:=2;
for i:=1 to k-sp do
begin
write(g,r+cs,' ');
r:=r+1;
if i mod 100=0 then writeln(g);
end;
for i:=k-sp+1 to k do
begin
write(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=11
Có 6 cách la:
2 2 2 2 3
2 2 2 5
2 2 7
2 3 3 3
3 3 5
11
function Nto(n:longint):boolean;
var i:integer;
begin
Nto:=false;
For i:=2 to round(sqrt(n)) do
If n mod i=0 then exit;
Nto:=true;
End;
Procedure Print;
Var i:integer;
Begin
For i:=1 to dem do write(g,a[i]:4);
Writeln(g);
End;
Procedure Phantich(n,k,d:integer);
Begin
A[d]:=k;
If n=0 then
Begin
Dem:=d;
Print;
End
Else
Begin
For i:=k to n do
If 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à:
Begin
For i:=k+1 to n do
If 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:
Begin
For 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:
Begin
For 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 do
if nto(i) then
begin
Fx[i]:=Fx[i]+1;
for j:=i+2 to n do
Fx[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;