4 Bài toán: Tổng củ an số tự nhiên sắp theo trật tự không tăng.
4.4 Thuật toán
Ta có phương án đầu tiên của giải thuật Bieudien như sau:
Đệ quy
function Bieudien(i,j: integer):longint; begin if j = 0 then Bieudien:=0 else {j > 0} if i = 0 then {i = 0; j > 0} Bieudien:=1 else {i,j > 0} if i < j then {0 < i < j}
Bieudien:=Bieudien(i,i) else {i >= j > 0}
Bieudien:=Bieudien(i,j-1)+Bieudien(i-j,j); end;
Biểu diễn với số tự nhiên (m = 7) và 4 số tự nhiên (n = 4). Thí dụ, hàm Bieudien(1,1) sẽ được gọi 9 lần,… Tổng số lần gọi hàm Bieudien là 79. 79 lần gọi hàm để sinh ra kết quả 11 là quá tốn kém. Ta có phương án đầu tiên của giải thuật Bieudien như sau:
Khá dễ triển khai nhưng chương trình sẽ chạy rất lâu, bạn hãy thử gọi Bieudien(66,32) để trải nghiệm được điều trên. Muốn thế chúng ta tính sẵn các giá trị của hàm theo các trị của đầu vào khác nhau và điền vào một mảng hai chiều Mang. Mảng Mang được mô tả như sau:
Gioihan
MN = 90;{gioi han tren cua m va n} Kieu
ml1 = array[0..MN ] of longint; ml2 = array[0..mn ] of ml1; var Mang: ml2;
Ta quy ước Mang[i, j] chứa số cách biễu diễn i số tự nhiên cho j số tự nhiên. Theo phân tích của phương án 1, ta có:
• Mang[0, 0] = 1; Mang[i, 0] = 0, với i:=1..m. • Mang[i, j] = Mang[i, i], nếu i < j
• Mang[i, j] = Mang[i, j - 1]+Mang[i - j, j], nếu i ≥ j Từ đó ta suy ra quy trình điền trị vào bảng Mang như sau:
Khởi trị
Mang[0, 0]:= 1;
với i:=1..m: Mang[i, 0]:= 0;
Điền bảng: Lần lượt điền theo từng cột j:= 1..n. Tại mỗi cột j ta đặt: với i:=0..j-1: Mang[i,j ]:= Mang[i, i];
với i:= j..m: Mang[i, j]:= Mang[i, j-1] + Mang[i-j, j];
Nhận kết quả: Sau khi điền bảng, giá trị Mang[m, n] chính là kết quả cần tìm.
Làm tốt 1:Dùng mảng 2 chiều
Mang[i,j] = số cách biễu diễn i số tự nhiên cho j số tự nhiên function Bieudien2(m,n: integer):longint;
var i,j: integer; begin
Mang[0,0 ]:=1;
for i:=1 to m do Mang[i,0]:=0; for j:=1 to n do
begin
for i:=0 to j-1 do Mang[i,j ]:=Mang[i,i ];
for i:=j to m do Mang[i,j ]:=Mang[i,j-1 ]+Mang[i-j,j ]; end;
Bieudien2:=Mang[m,n]; end;
Làm tốt lần 2: Dùng mảng hai chiều chúng ta chỉ có thể tính toán được với dữ liệu nhỏ. Bước cải tiến sau đây khá quan trọng: chúng ta dùng mảng một chiều. Quan sát kĩ quy trình gán trị cho mảng hai chiều theo từng cột chúng ta dễ phát hiện ra rằng cột thứ j có thể được tính toán từ cột thứ j - 1. Nếu gọi c là mảng một chiều sẽ dùng, ta cho số số tự nhiên tăng dần bằng cách lần lượt tính j bước, với j:= 1..n. Tại bước thứ j, c[i] chính là số cách biễu diễn i số tự nhiên cho j số tự nhiên. Như vậy, tại bước thứ j ta có:
• c[i] tại bước j = c[i] tại bước (j – 1), nếu i < j. Từ đây suy ra đoạn c[0..(j – 1)] được bảo lưu.
• c[i] tại bước j = c[i] tại bước (j – 1) + c[i – j] tại bước j, nếu i ≥ j.
Biểu thức thứ hai cho biết khi cập nhật mảng c từ bước thứ j – 1 qua bước thứ j ta phải tính từ trên xuống, nghĩa là tính dần theo chiều tăng của i:= j...m.
Mảng c được khởi trị ở bước j = 0 như sau: • c[0] = 1; c[i] = 0, với i:= 1..m.
Với ý nghĩa là, nếu có 0 số tự nhiên thì biễu diễn 0 số tự nhiên cho 0 số tự nhiên sẽ được quy định là 1. Nếu số số tự nhiên m khác 0 thì biễu diễn m số tự nhiên cho 0 số tự nhiên sẽ được 0 phương án.
Ta có phương án 3, dùng một mảng một chiều c như sau:
Làm tốt lần 3: dùng mảng 1 chiều c.
Tại bước thứ j, c[i ] = số cách biễu diễn i số tự nhiên cho j số tự nhiên.
function Bieudien1(m,n: integer):longint; var i,j: integer;
fillchar(c,sizeof(c),0); c[0 ]:=1;
for j:=1 to n do
for i:=j to m do c[i ]:=c[i ]+c[i-j ]; Bieudien1:=c[m ];
end