Cho dãy số nguyên A = a1, a2, …, an. (n ≤ 10000, -10000 ≤ ai ≤ 10000). Một dãy con của A là một cách chọn ra trong A một số phần tử giữ nguyên thứ tự. Như vậy A có 2n dãy con.
Yêu cầu: Tìm dãy con đơn điệu tăng của A có độ dài lớn nhất.
Ví dụ: A = (1, 2, 3, 4, 9, 10, 5, 6, 7, 8). Dãy con đơn điệu tăng dài nhất là: (1, 2, 3, 4, 5, 6, 7, 8).
Dữ liệu (Input) vào từ file văn bản INCSEQ.INP
• Dòng 1: Chứa số n
• Dòng 2: Chứa n số a1, a2, …, an cách nhau ít nhất một dấu cách
Kết quả (Output) ghi ra file văn bản INCSEQ.OUT
• Dòng 1: Ghi độ dài dãy con tìm được
• Các dòng tiếp: ghi dãy con tìm được và chỉ số những phần tử được chọn vào dãy con đó.
INCSEQ.INP | INCSEQ.OUT |
11 1 2 3 8 9 4 5 6 20 9 10 | 8 a[1] = 1 a[2] = 2 a[3] = 3 a[6] = 4 a[7] = 5 a[8] = 6 a[10] = 9 a[11] = 10 |
Cách giải:
Bổ sung vào A hai phần tử: a0 = -∞ và an+1 = +∞. Khi đó dãy con đơn điệu tăng dài nhất chắc chắn sẽ bắt đầu từ a0 và kết thúc ở an+1.
Với ∀ i: 0 ≤ i ≤ n + 1. Ta sẽ tính L[i] = độ dài dãy con đơn điệu tăng dài nhất bắt đầu tại ai.
1. Cơ sở quy hoạch động (bài toán nhỏ nhất):
L[n+1] = Độ dài dãy con đơn điệu tăng dài nhất bắt đầu tại an+1 = +∞. Dãy con này chỉ gồm mỗi một phần tử (+∞) nên L[n+1]=1.
2. Công thức truy hồi:
Giả sử với i từ n đến 0, ta cần tính L[i]: độ dài dãy con tăng dài nhất bắt đầu tại ai. L[i] được tính trong điều kiện L[i + 1], L[i + 2], …, L[n + 1] đã biết:
Dãy con đơn điệu tăng dài nhất bắt đầu từ ai sẽ được thành lập bằng cách lấy ai ghép vào đầu một trong số những dãy con đơn điệu tăng dài nhất bắt đầu tại vị trí aj đứng sau ai. Ta sẽ chọn dãy nào để ghép ai vào đầu? Tất nhiên là chỉ được ghép ai vào đầu những dãy con bắt đầu tại aj nào đó lớn hơn ai (để đảm bảo tính tăng) và dĩ nhiên ta sẽ chọn dãy dài nhất để ghép ai vào đầu (để đảm bảo tính dài nhất). Vậy L[i] được tính như sau: Xét tất cả các chỉ số j trong khoảng từ i + 1 đến n + 1 mà aj >ai, chọn ra chỉ số jmax có L[jmax] lớn nhất. Đặt L[i] := L[jmax] + 1.
3. Truy vết
Tại bước xây dựng dãy L, mỗi khi tính L[i] = L[jmax] + 1, ta đặt T[i] = jmax. Để lưu lại rằng: Dãy con dài nhất bắt đầu tại ai sẽ có phần tử thứ hai kế tiếp là ajmax.
Sau khi tính xong hay dãy L và T, ta bắt đầu từ 0.
T[0] là phần tử đầu tiên được chọn,
T[T[0]] là phần tử thứ hai được chọn,
T[T[T[0]]] là phần tử thứ ba được chọn …Quá trình truy vết có thể diễn tả như sau:
i := T[0];
while i <> n + 1 do {Chừng nào chưa duyệt đến số an+1=+∞ ở cuối}
begin
<Thông báo chọn ai>
i := T[i];
end;
while i <> n + 1 do {Chừng nào chưa duyệt đến số an+1=+∞ ở cuối}
begin
<Thông báo chọn ai>
i := T[i];
end;
Ví dụ: với A = (5, 2, 3, 4, 9, 10, 5, 6, 7, 8). Hai dãy L và T sau khi tính sẽ là:
PROG03_1.PAS * Tìm dãy con đơn điệu tăng dài nhất
program LongestSubSequence;
const
max = 10000;
var
a, L, T: array[0..max + 1] of Integer;
n: Word;
procedure Enter; {Nhập dữ liệu từ thiết bị nhập chuẩn theo đúng khuôn dạng Input}
var
i: Word;
begin
ReadLn(n);
for i := 1 to n do Read(a[i]);
end;
procedure Optimize; {Quy hoạch động}
var
i, j, jmax: Word;
begin
a[0] := -32768; a[n + 1] := 32767; {Thêm hai phần tử canh hai đầu dãy a}
L[n + 1] := 1; {Điền cơ sở quy hoach động vào bảng phương án}
for i := n downto 0 do {Tính bảng phương án}
begin
{Chọn trong các chỉ số j đứng sau i thoả mãn aj > ai ra chỉ số jmax có L[jmax] lớn nhất}
jmax := n + 1;
for j := i + 1 to n + 1 do
if (a[j] > a[i]) and (L[j] > L[jmax]) then jmax := j;
L[i] := L[jmax] + 1; {Lưu độ dài dãy con tăng dài nhất bắt đầu tại ai}
T[i] := jmax; {Lưu vết: phần tử đứng liền sau ai trong dãy con tăng dài nhất đó là ajmax}
end;
WriteLn(L[0] - 2); {Chiều dài dãy con tăng dài nhất}
i := T[0]; {Bắt đầu truy vết tìm nghiệm}
while i <> n + 1 do
begin
WriteLn('a[', i, '] = ', a[i]);
i := T[i];
end;
end;
begin
{Định nghĩa lại thiết bị nhập/xuất chuẩn}
Assign(Input, 'INCSEQ.INP'); Reset(Input);
Assign(Output, 'INCSEQ.OUT'); Rewrite(Output);
Enter;
Optimize;
Close(Input); Close(Output);
end.
const
max = 10000;
var
a, L, T: array[0..max + 1] of Integer;
n: Word;
procedure Enter; {Nhập dữ liệu từ thiết bị nhập chuẩn theo đúng khuôn dạng Input}
var
i: Word;
begin
ReadLn(n);
for i := 1 to n do Read(a[i]);
end;
procedure Optimize; {Quy hoạch động}
var
i, j, jmax: Word;
begin
a[0] := -32768; a[n + 1] := 32767; {Thêm hai phần tử canh hai đầu dãy a}
L[n + 1] := 1; {Điền cơ sở quy hoach động vào bảng phương án}
for i := n downto 0 do {Tính bảng phương án}
begin
{Chọn trong các chỉ số j đứng sau i thoả mãn aj > ai ra chỉ số jmax có L[jmax] lớn nhất}
jmax := n + 1;
for j := i + 1 to n + 1 do
if (a[j] > a[i]) and (L[j] > L[jmax]) then jmax := j;
L[i] := L[jmax] + 1; {Lưu độ dài dãy con tăng dài nhất bắt đầu tại ai}
T[i] := jmax; {Lưu vết: phần tử đứng liền sau ai trong dãy con tăng dài nhất đó là ajmax}
end;
WriteLn(L[0] - 2); {Chiều dài dãy con tăng dài nhất}
i := T[0]; {Bắt đầu truy vết tìm nghiệm}
while i <> n + 1 do
begin
WriteLn('a[', i, '] = ', a[i]);
i := T[i];
end;
end;
begin
{Định nghĩa lại thiết bị nhập/xuất chuẩn}
Assign(Input, 'INCSEQ.INP'); Reset(Input);
Assign(Output, 'INCSEQ.OUT'); Rewrite(Output);
Enter;
Optimize;
Close(Input); Close(Output);
end.
Cài đặt bằng ngôn ngữ C++
#include <iostream>
#include <fstream>
using namespace std;
#define Input "INCSEQ.INP"
#define Output "INCSEQ.OUT"
int main()
{
int A[10002],L[10002],T[10002],n;
ifstream fi(Input);
fi>>n;
for (int i=1; i<=n; i++)
fi>>A[i];
A[0]=-(1<<31);
A[n+1]=(1<<31)-1;
fi.close();
for (int i=0; i<=n; i++) L[i]=0;
L[n+1]=1;
for (int i=n; i>=0; i--)
{
int jmax=i;
for (int j=i+1; j<=n+1; j++)
if (A[i]<A[j] && L[j]>L[jmax])
jmax=j;
L[i]=L[jmax]+1;
T[i]=jmax;
}
ofstream fo(Output);
fo<<L[0]-1<<endl;
int i=0;
while (T[i]<n+1)
{
fo<<"A["<<T[i]<<"]="<<A[T[i]]<<endl;
i=T[i];
}
fo.close();
}
#include <fstream>
using namespace std;
#define Input "INCSEQ.INP"
#define Output "INCSEQ.OUT"
int main()
{
int A[10002],L[10002],T[10002],n;
ifstream fi(Input);
fi>>n;
for (int i=1; i<=n; i++)
fi>>A[i];
A[0]=-(1<<31);
A[n+1]=(1<<31)-1;
fi.close();
for (int i=0; i<=n; i++) L[i]=0;
L[n+1]=1;
for (int i=n; i>=0; i--)
{
int jmax=i;
for (int j=i+1; j<=n+1; j++)
if (A[i]<A[j] && L[j]>L[jmax])
jmax=j;
L[i]=L[jmax]+1;
T[i]=jmax;
}
ofstream fo(Output);
fo<<L[0]-1<<endl;
int i=0;
while (T[i]<n+1)
{
fo<<"A["<<T[i]<<"]="<<A[T[i]]<<endl;
i=T[i];
}
fo.close();
}
Nguồn: Bài giảng chuyên đề Quy hoạch động – Lê Minh Hoàng
0 nhận xét