动态规划程序设计(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.
我的板子 O(n²)

【例】拦截导弹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.
【参考程序】(逆推法)
原文地址:https://www.cnblogs.com/vacation/p/6052621.html