Thuật toán Phương án đệ quy

Một phần của tài liệu Phương pháp quy hoạch động và một số ví dụ cụ thể (Trang 52)

n chíh là phầ tử C[, k] trog mảg

1.13Thuật toán Phương án đệ quy

Phương án đệ quy

Phương án đệ quy dưới đây như mô tả trong hàm nguyên rec(i, j) tính trực tiếp giá trị p(i, j) theo các tính chất đã liệt kê. Đáp số cho bài toán khi đó sẽ là

n-rec(1,n). function rec(i,j: integer): integer;

begin

if i > j then rec:=0

else if i = j then rec:=1 else {i < j}

if s[i ] = s[j ] {i < j & s[i ] = s[j ]} then rec:=rec(i+1,j-1)+2

else {i < j & s[i ] s[j ]} rec:=max(rec(i,j-1),rec(i+1,j)); end;

Ta có bảng mô tả như sau:

Dòng i [i,j-1] [i,j] Dòng i+1 [i+1,j-1] [i+1,j]

Dùng một mảng hai chiều

Gọi đệ quy sẽ phát sinh các lời gọi hàm trùng lặp như đã phân tích trong bài toán. Ta khắc phục điều này bằng cách sử dụng một mảng hai chiều để tính trước các giá trị của hàm p(i, j), mỗi giá trị được tính tối đa một lần. Nếu dùng một mảng hai chiều, thí dụ mảng p[0..n, 0..n] thì giá trị của p[i, j] sẽ được điền lần lượt theo từng cột, từ cột thứ 1 đến cột thứ n. Tại mỗi cột ta điền từ dưới lên trên. Ta lưu ý:

• Phần tử tại cột i, dòng j là giá trị p[i, j] là chiều dài của dãy con dài nhất khi khảo sát dãy con s[i..j].

• Với các trị i > j, ta quy định p[i, j] = 0. Như vậy nửa tam giác dưới của ma trận p sẽ chứa toàn 0.

• Nếu i = j thì p[i, j] = 1. Như vậy, mọi trị trên đường chéo chính của ma trận p sẽ là 1.

• Với các ô còn lại toạ độ (i, j) sẽ thoả điều kiện i < j, nên p[i, j] sẽ được tính như sau:

if s[i ] = s[j ] then p[i,j ] = p[i+1,j-1 ]+2 else p[i,j]:= max(p[i,j-1],p[i+1,j ])

Bạn hãy thử điền một vài giá trị cho bảng trên để rút ra quy luật. Hãy bắt đầu với cột 1: p[1, 1] = 0;

Sau đó đến cột 2: p[2, 2] = 1; p[1, 2] = max(p[1, 1], p[2, 2]) = 1, vì s[1] ≠ s[2]. Rồi đến cột 3: p[3,3]=1; p[2,3] = max(p[2, 2], p[3, 3]) = 1, vì s[2] ≠ s[3]; p[1,3] = max(p[1,2], p[2,3]) = 1, vì s[1] ≠ s[3],… Dùng hai mảng một chiều

Ta sẽ không theo đuổi phương án dùng mảng hai chiều mà hãy căn cứ vào quy luật điền mảng hai chiều để vận dụng cho hai mảng một chiều là v[0..(n + 1)] và d[0..(n + 1)]. Theo kinh nghiệm, ta nên khai báo kích thước mảng rộng hơn chừng hai phần tử để sử dụng các phần tử này như những lính canh chứa các giá trị khởi đầu phục vụ cho các trường hợp chỉ số i, j nhận các giá trị 0 hoặc n + 1.

Giả sử mảng v chứa các giá trị đã điền của cột j – 1 trong mảng hai chiều p. Ta sẽ điền các giá trị cho cột j của mảng p vào mảng một chiều d. Như vậy, phần tử v[i] sẽ ứng với phần tử p[j – 1, i] còn phần tử d[i] sẽ ứng với p[j, i]. Thủ tục điền trị cho cột d tại bước j dựa theo kết quả lưu trong cột v của bước j – 1 khi đó sẽ như sau:

for i:=j-1 downto 1 do begin

if s[i ]=s[j ] then d[i ]:=v[i+1 ]+2 else d[i ]:=max(v[i ],d[i+1 ]); end;

Sau mỗi lần lặp với j:=1..n ta chuyển giá trị của d cho v để chuẩn bị cho bước tiếp theo.

procedure QHD2; var i,j: integer; begin

fillchar(v,sizeof(v),0); for j:=1 to n do

begin

d[j ]:=1;

for i:=j-1 downto 1 do begin

if s[i ]= s[j ] then d[i ]:=v[i+1 ]+2 else d[i ]:=max(v[i ],d[i+1 ]); end;

v:=d; end;

writeln(nl,n-d[1 ]); {dap so} end;

Dùng một mảng một chiều

Có thể chỉ sử dụng một mảng một chiều d cho bài toán này với nhận xét sau đây. Tại bước cập nhật thứ j, với mỗi i = (j – 1)..1 ta có d[i] = p[i, j] và được tính như sau:

• Nếu s[i] = s[j] thì d[i] tại bước j bằng d[i + 1] tại bước j – 1 cộng với 2. • Nếu s[i] ≠ s[j] thì

d[i] tại bước j bằng max(d[i] tại bước j – 1, d[i + 1] tại bước j).

Nếu ta tính từ dưới lên, tức là tính d[i] với i = n..1 thì d[i + 1] cũ sẽ bị ghi đè. Ta dùng hai biến phụ t và tr để bảo lưu giá trị này.

procedure QHD1; var i,j,t,tr: integer; begin

begin tr:=0; d[j ]:=1;

for i:=j-1 downto 1 do begin

t:=d[i ];

if s[i ]=s[j ] then d[i ]:=tr+2

else d[i ]:=max(d[i ],d[i+1 ]); tr:=t;

end; end;

writeln(nl,n-d[1 ]); {dap so} end;

Dĩ nhiên phương án dùng một mảng một chiều sẽ được coi trọng tiết kiệm được miền nhớ. Tuy nhiên, tinh ý một chút, bạn sẽ phát hiện ra rằng thời gian tính toán

Dĩ nhiên phương án dùng một mảng một chiều sẽ được coi trọng tiết kiệm được miền nhớ. Tuy nhiên, tinh ý một chút, bạn sẽ phát hiện ra rằng thời gian tính toán theo phương án này không ít hơn phương án dùng hai mảng một chiều. Thật vậy, để tính mỗi phần tử ta phải dùng thêm hai phép gán, trong khi dùng hai mảng một chiều ta chỉ phải thêm một phép gán cho mỗi phần tử. Hơn nữa, dùng hai mảng một chiều thường tránh được nhầm lẫn, do đó nhiều người thường chọn phương án này.

Trong bài toán này ta sử dụng tư tưởng quy hoạch động vào xây dựng công thức truy hồi, và kiểm tra lại bằng đệ quy cùng một kết quả. Nên giải thuật chúng ta sử dụng trên luôn cho kết quả đúng.

1.15 Độ phức tạp thuật toán

Độ phức tạp Đệ quy Quy hoạch động Quy hoạch động cải tiến

Không gian O(2n) O(n.m) O(m)

Thời gian O(2n) O(n.m) O(n.m)

Bài toán với ba phương án, đệ quy, dùng hai mảng một chiều và dùng một mảng một chiều khi đó sẽ như sau.

Dữ liệu kiểm thử Kết quả dự kiến 9

baeadbadb

2 Bài toán: Người nông dân 2.1 Phát biểu bài toán

Một người nông dân có một số cánh đồng, mỗi cánh đồng được bao quanh bởi các hàng cây bách. Ngoài ra ông ta có tập các dải đất, mỗi một dãi đất có một hàng cây bách. Trên cánh đồng và giải đất, xen giữa hai cây bách là một cây oliu. Tất cả các cây bách hoặc bao quanh cánh đồng hoặc nằm trên dải đất và tất cả cây oliu đều trồng xem giữa hai cây bách liên tiếp.

Một ngày nọ người nông dân ốm nặng và cảm thấy sắp phải đi xa. Vài ngày trước khi qua đời ông gọi người con trai lớn nhất đến và nói với anh ta “Ta cho con chọn Q cây bách bất kỳ và tất cả cây oliu nằm giữa 2 cây bách liên tiếp mà con đã chọn đều thuộc về con”.Người con có thể chọn tổ hợp các cây bách bất kỳ từ cánh đồng và dải đất, vì người con trai này rất thích cây oliu nên anh ta chọn Q cây bách sao cho thừa hưởng được nhiều cây bách nhất có thể.

Trong hình dưới giả thiết, giả sử người con chọn được 17 cây bách (Q=17) để có được số cây oliu lớn nhất anh ta phải chọn trong cánh đồng 1 và cánh đồng 2, với cách chọn này anh ta sẽ thừa hưởng được 17 cây oliu. Cho trước thông tin về cánh đồng, dải đất và số cây bách người con được chọn. Hãy xác định số cây oliu lớn nhất mà người con được chọn.

2.2 Ý tưởng 30s

Phương pháp đầu tiên ta nghĩ đến là thử rồi liệt kê tất cả các trường hợp cây oliu. Nhưng với số 17 cây bách chẳng hạn thì số tổ hợp của nó cũng rất lớn ta không thể nào tính được. Liệu có cách nào giải quyết bài toán này hiệu quả hơn không.

• Dòng đầu tiên bao gồm số nguyên Q ( 0 < Q ≤ 150000): là số cây bách người con được chọn.

• Sau đó số nguyên M là số các cánh đồng.

• Tiếp theo là số nguyên K sô các dải đất.

• Dòng thứ 2 chứa M số nguyên N1,N2…NM (3 ≤ N1,…NM ≤ 150) là số các cây bách trên cánh đồng.

• Dòng thứ 2 chứa K số nguyên N1,N2…NM (2 ≤ R1,…RM ≤ 150) là số các cây bách trên dải đất.

Chú ý: tổng số cẩy bách trên cánh đồng và dải đất ít nhất cũng bằng Q.

Output:

Là một số nguyên số cây oliu duy nhất mà người con có thể hưởng.

Input Output

17 3 313 4 8 13 4 8

4 8 6 17

Một phần của tài liệu Phương pháp quy hoạch động và một số ví dụ cụ thể (Trang 52)