动态规划程序设计(5)
【例】求最长不下降序列
【问题描述】:
设有由n个不相同的整数组成的数列,记为:b(1)、b(2)、……、b(n)且b(i)<>b(j) (i<>j),若存在i1<i2<i3< … < ie 且有b(i1)<b(i2)< … <b(ie)则称为长度为e的不下降序列。程序要求,当原数列给出之后,求出最长的不下降序列。
例如13,7,9,16,38,24,37,18,44,19,21,22,63,15。例中13,16,18,19,21,22,63就是一个长度为7的不下降序列,同时也有7 ,9,16,18,19,21,22,63长度为8的不下降序列。
【算法分析】:根据动态规划的原理,由后往前进行搜索。
1)、对b(n)来说,由于它是最后一个数,所以当从b(n)开始查找时,只存在长度为1的不下降序列;
2)、若从b(n-1)开始查找,则存在下面的两种可能性:
①若b(n-1)<b(n)则存在长度为2的不下降序列b(n-1),b(n)。
②若b(n-1)>b(n)则存在长度为1的不下降序列b(n-1)或b(n)。
3)、一般若从b(i)开始,此时最长不下降序列应该按下列方法求出:
在b(i+1),b(i+2),…,b(n)中,找出一个比b(i)大的且最长的不下降序列,作为它的后继。
【数据结构】:
为算法上的需要,定义一个数组整数类型二维数组b(N,3)
1)、b(i,1)表示第i个数的数值本身;
2)、b(i,2)表示从i位置到达N的最长不下降序列长度
3)、b(i,3)表示从i位置开始最长不下降序列的下一个位置,若b[i,3]=0则表示后面没有连接项。
【求解过程】:
① 从倒数第二项开始计算,后面仅有1项,比较一次,因63>15,不符合要求,长度仍为1
② 从倒数第三项开始其后有2项,需做两次比较,得到目前最长的不下降序列为2,如下表:
|
11 |
12 |
13 |
14 |
…… |
|
11 |
12 |
13 |
14 |
|
|
22 |
63 |
15 |
…… |
|
21 |
22 |
63 |
15 |
|
|
2 |
1 |
1 |
…… |
|
3 |
2 |
1 |
1 |
|
|
13 |
0 |
0 |
…… |
|
12 |
13 |
0 |
0 |
【一般处理过程】:
①在i+1,i+2,…,n项中,找出比b[I,1]大的最长长度L以及位置K;
②若L>0,则b[I,2]:=L+1;b[I,3]:=k;
最后本题经过计算,其数据存储表如下:
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
13 |
7 |
9 |
16 |
38 |
24 |
37 |
18 |
44 |
19 |
21 |
22 |
63 |
15 |
7 |
8 |
7 |
6 |
3 |
4 |
3 |
5 |
2 |
4 |
3 |
2 |
1 |
1 |
4 |
3 |
4 |
8 |
9 |
7 |
9 |
10 |
13 |
11 |
12 |
13 |
0 |
0 |
1 初始化:
2 for i:=1 to n do
3 begin
4 read(b[i,1]);
5 b[i,2]:=1;b[i,3]:=0;
6 end;
7 下面给出求最长不下降序列的算法:
8 for i:=n-1 downto 1 do
9 begin
10 L:=0;k:=0;
11 for j:=i+1 to n do
12 if(b[j,1]>b[i,1])and(b[j,2]>L) then begin
13 L:=b[j,2];
14 k:=j;
15 end;
16 if L>0 then begin
17 b[i,2]:=L+1;
18 b[i,3]:=k;
19 end;
20 end;
21 下面找出最长不下降序列:
22 k:=1;
23 for j:=1 to n do
24 if b[j,2]>b[k,2] then k:=j;
25 最长不下降序列长度为B(k, 2)序列
26 while k<>0 do
27 begin
28 write(b[k,1]:4);
29 k:=b[k,3];
30 end;
1 var 2 n,i,L,k,j:integer; 3 b:array[1..100,1..3]of integer; 4 begin 5 readln(n); 6 for i:=1 to n do 7 begin 8 read(b[i,1]); 9 b[i,2]:=1;b[i,3]:=0; 10 end; 11 for i:=n-1 downto 1 do 12 begin 13 L:=0;k:=0; 14 for j:=i+1 to n do 15 if(b[j,1]>b[i,1])and(b[j,2]>L) then begin 16 L:=b[j,2]; k:=j; 17 end; 18 if L>0 then begin 19 b[i,2]:=L+1;b[i,3]:=k; 20 end; 21 end; 22 k:=1; 23 for j:=1 to n do 24 if b[j,2]>b[k,2] then k:=j; 25 writeln('max=',b[k,2]); 26 while k<>0 do 27 begin 28 write(b[k,1]:4); 29 k:=b[k,3]; 30 end; 31 writeln; 32 end.
程序运行结果:
输入:14
13 7 9 16 38 24 37 18 44 19 21 22 63 15
输出:max=8
7 9 16 18 19 21 22 63
1 var 2 i,n,max,st,en:longint; 3 b:array[1..100000,1..3] of longint; 4 procedure Init; 5 var i:longint; 6 begin 7 readln(n); 8 for i:=1 to n do 9 begin 10 read(b[i,1]); 11 b[i,2]:=1; b[i,3]:=0; 12 end; 13 end; 14 procedure Lis; 15 var i,j,maxl,loca:longint; 16 begin 17 for i:=n-1 downto 1 do 18 begin 19 maxl:=0; loca:=0; 20 for j:=i+1 to n do 21 if b[i,1]<b[j,1] then 22 //begin 23 if b[j,2]>maxl then 24 begin 25 maxl:=b[j,2]; 26 loca:=j; 27 end; 28 //end; 29 if maxl>0 then 30 begin 31 b[i,2]:=maxl+1; 32 b[i,3]:=loca; 33 end; 34 end; 35 end; 36 procedure Findmax; 37 var i:longint; 38 begin 39 max:=0; 40 for i:=1 to n do 41 if b[i,2]>max then 42 begin 43 max:=b[i,2]; 44 st:=i; 45 end; 46 end; 47 procedure Outit; 48 var i:longint; 49 begin 50 writeln(max); 51 i:=st; 52 while i<>0 do 53 begin 54 write(b[i,1],' '); 55 i:=b[i,3]; 56 end; 57 end; 58 begin 59 Init; 60 Lis; 61 Findmax; 62 Outit; 63 end.
【例】拦截导弹1
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭,由于该系统还在试用阶段。所以只有一套系统,因此有可能不能拦截所有的导弹。输入导弹依次飞来的高度(雷达给出的高度不大于30000的正整数)。计算这套系统最多能拦截多少导弹。
输入:N颗依次飞来的导弹高度,(导弹个数<=1000)。
输出:一套系统最多拦截的导弹数,并依次打印输出被拦截导弹的高度。
在本题中不仅要求输出最优解,而且还要求输出最优解的形成过程。为此,我们设置了一张记忆表C[i],在按从后往前方式求解的过程中,将每一个子问题的最佳决策保存起来,避免在输出方案时重复计算。
阶段i: 由右而左计算导弹n‥导弹1中可拦截的最多导弹数(1≤i≤n);
状态B[i]: 由于每个阶段中仅一个状态,因此可通过一重循环
for i := n-1 downto 1 do 枚举每个阶段的状态B[i];
决策k:在拦截导弹i之后应拦截哪一枚导弹可使得B[i]最大(i+1≤k≤n),
1 2 3 4 5 6 7 8 9 10 11 12 13 14 I
13 7 9 16 38 24 37 18 44 19 21 22 63 15 A[I] {高度}
2 1 1 2 4 3 3 2 3 2 2 2 2 1 B[I] {可拦截数}
2 0 0 14 6 8 8 14 10 14 14 14 14 0 C[I] {再拦截}
1 var 2 a,b,c:array[1..1000] of longint; 3 n,i,j,k,max:longint; 4 begin 5 n:=0; {初始化,读入数据} 6 while not eoln do 7 begin {eoln :行结束} 8 inc(n);read(a[n]); 9 b[n]:=1; c[n]:=0; 10 end; 11 readln; 12 for i:=n-1 downto 1 do {枚举每一个阶段的状态,设导弹i被拦截} 13 begin 14 max:=0; j:=0; 15 for k:=i+1 to n do {枚举决策,计算最佳方案中拦截的下一枚导弹} 16 if (a[k]<=a[i]) and (b[k]>max) then begin max:=b[k];j:=k; end; 17 b[i]:=max+1; c[i]:=j; {若导弹i之后拦截导弹j为最佳方案,则记下} 18 end; 19 max := 0; 20 for i := 1 to n do {枚举求出一套系统能拦截的最多导数} 21 if b[i]>max then begin max:=b[i]; j:=i; end; 22 writeln('Max = ',b[j]); {打印输出结果} 23 while j>0 do 24 begin 25 write(a[j]:5); j:=c[j]; 26 end; 27 end.