动态规划初步

一、动态规划简介

  动态规划是运筹学的一个分支。它是解决多阶段决策过程最优化问题的一种方法。1951年,美国数学家贝尔曼(R.Bellman)提出了解决这类问题的“最优化原则”,1957年发表了他的名著《动态规划》,该书是动态规划方面的第一本著作。动态规划问世以来,在工农业生产、经济、军事、工程技术等许多方面都得到了广泛的应用,取得了显著的效果。

  动态规划运用于信息学竞赛是在90年代初期,它以独特的优点获得了出题者的青睐。此后,它就成为了信息学竞赛中必不可少的一个重要方法,几乎在所有的国内和国际信息学竞赛中,都至少有一道动态规划的题目。所以,掌握好动态规划,是非常重要的。

动态规划是一种方法,是考虑问题的一种途径,而不是一种算法。因此,它不像深度优先和广度优先那样可以提供一套模式。它必须对具体问题进行具体分析。需要丰富的想象力和创造力去建立模型求解。

[8-1] 拦截导弹

       某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

       输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

样例:

INPUT                                    OUTPUT

389  207  155  300  299  170  158  65     6(最多能拦截的导弹数)

                                                                    2(要拦截所有导弹最少要配备的系统数)

[问题分析]

    第一部分是要求输入数据串中的一个最长不上升序列的长度,可使用递推的方法,具体做法是从序列的第一个元素开始,依次求出以第i个元素为最后一个元素时的最长不上升序列的长度len(i),递推公式为len(1)=1len(i)=max(len(j)+1),其中i>1j=1,2,i-1,且j同时要满足条件:序列中第j个元素大于等于第i个元素。

    第二部分比较有意思。由于它紧接着第一问,所以很容易受前面的影响,采取多次求最长不上升序列的办法,然后得出总次数,其实这是不对的。要举反例并不难,比如长为7的高度序列“7 5 4 1 6 3 2”, 最长不上升序列为“7 5 4 3 2”,用多次求最长不上升序列的结果为3套系统;但其实只要2套,分别击落“7 5 4 1”与“6 3 2”。那么,正确的做法又是什么呢?

我们的目标是用最少的系统击落所有导弹,至于系统之间怎么分配导弹数目则无关紧要;上面错误的想法正是承袭了“一套系统尽量多拦截导弹”的思维定势,忽视了最优解中各个系统拦截数较为平均的情况,本质上是一种贪心算法。如果从每套系统拦截的导弹方面来想行不通的话,我们就应该换一个思路,从拦截某个导弹所选的系统入手。

题目告诉我们,已有系统目前的瞄准高度必须不低于来犯导弹高度,所以,当已有的系统均无法拦截该导弹时,就不得不启用新系统。如果已有系统中有一个能拦截该导弹,我们是应该继续使用它,还是另起炉灶呢?事实是:无论用哪套系统,只要拦截了这枚导弹,那么系统的瞄准高度就等于导弹高度,这一点对旧的或新的系统都适用。而新系统能拦截的导弹高度最高,即新系统的性能优于任意一套已使用的系统。既然如此,我们当然应该选择已有的系统。如果已有系统中有多个可以拦截该导弹,究竟选哪一个呢?当前瞄准高度较高的系统的“潜力”较大,而瞄准高度较低的系统则不同,它能打下的导弹别的系统也能打下,它够不到的导弹却未必是别的系统所够不到的。所以,当有多个系统供选择时,要选瞄准高度最低的使用,当然瞄准高度同时也要大于等于来犯导弹高度。

解题时,用一个数组记下已有系统的当前瞄准高度,数据个数就是系统数目。

[程序清单]

const max=1000;

var i,j,current,maxlong,minheight,select,tail,total:longint;

    height,longest,sys:array [1..max] of longint;

    line:string;

begin

     write('Input test data:');

     readln(line);

     i:=1;

     while i<=length(line) do

     begin

          while (i<=length(line)) and (line[i]=' ') do i:=i+1;

          current:=0;

          while (i<=length(line)) and (line[i]<>' ') do

          begin

               current:=current*10+ord(line[i])-ord('0');

               i:=i+1

          end;

          total:=total+1;

          height[total]:=current

     end;

     longest[1]:=1;

     for i:=2 to total do

     begin

          maxlong:=1;

          for j:=1 to i-1 do

          begin

               if height[i]<=height[j]

                  then if longest[j]+1>maxlong

                          then maxlong:=longest[j]+1;

               longest[i]:=maxlong

         end;

     end;

     maxlong:=longest[1];

     for i:=2 to total do

         if longest[i]>maxlong then maxlong:=longest[i];

     writeln(maxlong);

     sys[1]:=height[1]; tail:=1;

     for i:=2 to total do

     begin

          minheight:=maxint;

          for j:=1 to tail do

              if sys[j]>height[i] then

                 if sys[j]<minheight then

                    begin minheight:=sys[j]; select:=j end;

          if minheight=maxint

             then begin tail:=tail+1; sys[tail]:=height[i] end

             else sys[select]:=height[i]

     end;

     writeln(tail)

end.

 

二、动态规划的几个基本概念

    想要掌握好动态规划,首先要明白几个概念:阶段、状态、决策、策略、指标函数。

1.阶段:把所给问题的过程,恰当地分为若干个相互联系的阶段,以便能按一定的次序去求解。描述阶段的变量称为阶段变量。

2.状态:状态表示每个阶段开始所处的自然状况和客观条件,它描述了研究问题过程中的状况,又称不可控因素。

3.决策:决策表示当过程处于某一阶段的某个状态时,可以作出不同的决定(或选择),从而确定下一阶段的状态,这种决定称为决策,在最优控制中也称为控制。描述决策的变量,称为决策变量。

4.策略:由所有阶段的决策组成的决策函数序列称为全过程策略,简称策略。

5.状态转移方程:状态转移方程是确定过程由一个状态到另一个状态的演变过程。

6.指标函数:用来衡量所实现过程优劣的一种数量指标,称为指标函数。指标函数的最优值,称为最优值函数。

三、确定动态规划的思路

1、采用动态规划来解决问题,必须符合两个重要的条件。

1)“过去的历史只能通过当前状态去影响它未来的发展,当前的状态是对以往历史的一个总结”,这种特性称为无后效性,是多阶段决策最优化问题的特征。

2)作为整个过程的最优策略具有这样的性质:即无论过去的状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简言之,一个最优策略的子策略总是最优的。这就是最优化原理。

2、如果碰到一个问题,能够满足以上两个条件的话,那么就可以去进一步考虑如何去设计使用动态规划:

1)划分阶段。把一个问题划分成为许多阶段来思考

2)设计合适的状态变量(用以递推的角度)

3)建立状态转移方程(递推公式)

4)寻找边界条件(已知的起始条件)

如果以上几个步骤都成功完成的话,我们就可以进行编程了。

四、动态规划解题的一些技巧

    由于动态规划并没有一个定式,这就需要去开拓我们创造力去构造并且使用它。以下,通过一些具体的竞赛实例谈谈使用动态规划过程中的一些技巧。

[ 8-2] 堆塔问题,设有n个边长为1的正立方体,在一个宽为1的轨道上堆塔,但塔本身不能分离。

    例如n=1时,只有1种方案 □

         n=2  2种方案   

        堆塔的规则为底层必须有支撑,如下列的堆法是合法的.                                    

    下面的堆法是不合法的.

 

 

    程序要求:输入n(n<=40),求出

    ① 总共有多少种不同的方案

    ② 堆成每层的方案数是多少,例如n=6,堆成1层的方案数为1,……堆成6层的方案数为1……

[问题分析] 问题①的分析见第七讲例7-3,关于问题②,可以这样考虑,将n个立方体的第一列去掉的话,则成为一个比n小的堆塔问题,这样问题②就可以用动态规划法来解。具体方法是从1n依次求出堆成每层的方案数,设f(n,k)为堆成k层的方案数,则递推公式为:

[算法设计] 由于n最大可达到40,所以堆塔的总方案数超过了长整形数的范围,程序采用了高精度加法运算。

[程序清单]

program ex8_2(input,output);

const maxn=40; maxlen=maxn div 3;

type arraytype=array[0..maxlen] of integer;

var i,j,k,n,nn:longint;

    total:arraytype;

    f:array [0..maxn,0..maxn] of arraytype;

procedure add(var a:arraytype;b:arraytype);

var i:longint;

begin

     for i:=0 to maxlen do a[i]:=a[i]+b[i];

     for i:=0 to maxlen-1 do

         if a[i]>=10 then

         begin

              a[i+1]:=a[i+1]+1;

              a[i]:=a[i]-10

         end

end;

procedure print(a:arraytype);

var i,j:longint;

begin

     i:=maxlen;

     while (i>0) and (a[i]=0) do i:=i-1;

     for j:=i downto 0 do write(a[j])

end;

begin

     write('Input n:');

     readln(n);

     for i:=1 to maxlen do total[i]:=0;

     total[0]:=1;

     for i:=1 to n-1 do add(total,total);

     for i:=1 to maxn do

         for j:=1 to maxn do

             for k:=0 to maxlen do f[i,j,k]:=0;

     for i:=1 to maxn do f[i,1,0]:=1;

     for i:=1 to maxn do f[i,i,0]:=1;

     for nn:=2 to n do

         for k:=2 to nn-1 do

         begin

              for i:=1 to k-1 do add(f[nn,k],f[nn-i,k]);

              for i:=1 to k do

                  if nn-k>=i then add(f[nn,k],f[nn-k,i])

         end;

     write('Total=');

     print(total);

     writeln;

     for k:=1 to n do

     begin

          write('Height=',k:2,'Kind=':10);

          print(f[n,k]);

          writeln;

          if k mod 23=0

             then

             begin

                  write('Press <Enter> to continue!');

                  readln

             end

     end;

     write('Press <Enter> to continue!');

     readln

end.

[8-3] 投资问题:

    n万元的资金,可投资于m个项目,其中m n为小于100的自然数。对第i1im)个项目投资j万元(1jn,且 j为整数)可获得的回报为Qi , j),请编程序,求解并输出最佳的投资方案(即获得回报总值最高的投资方案)。

    输入数据放在一个文本文件中,格式如下:

m  n

Q(1 , 0)   Q(1 , 1)······Q(1 , n)

Q(2 , 0)   Q(2 , 1)······Q(2 , n)

············

Q(m , 0)  Q(m , 1)······Q(m , n)

输出数据格式为:

r(1)  r(2) ······ r(m)  P

    其中r(i)1im)表示对第i个项目的投资万元数,P为总的投资回报值,保留两位有效数字,任意两个数之间空一格。当存在多个并列的最佳投资方案时,只要求输出其中之一即可。如输入数据如下时:

2  3

0  1.1  1.3  1.9

0  2.1  2.5  2.6

屏幕应输出:1  2  3.6

[问题分析] 本题可供考虑的递推角度只有资金和项目两个角度,从项目的角度出发,逐个项目地增加可以看出只要算出了对前k个项目投资j万元最大投资回报值(1jn),就能推出增加第k+1个项目后对前k+1个项目投资j万元最大投资回报值(1jn),设P[k,j]为前k个项目投资j万元最大投资回报值,则P[k+1,j]= Max(P[k,i]+Q[k+1,j-i]),0<=i<=jk=1时,对第一个项目投资j万元最大投资回报值(0jn)是已知的(边界条件)。

[算法设计] 动态规划的时间复杂度相对于搜索算法是大大降低了,却使空间消耗增加了很多。所以在设计动态规划的时候,需要尽可能节省空间的占用。本题中如果用二维数组存储原始数据和最大投资回报值的话,将造成空间不够,事实上,从前面的问题分析可知:在计算对前k+1个项目投资j万元最大投资回报值时,只要用到矩阵Q的第k+1行数据和对前k个项目投资j万元最大投资回报值,而与前面的数据无关,后者也只需有一个一维数组存储即可,程序中用一维数组Q存储当前项目的投资回报值,用一维数组maxreturn存储对当前项目之前的所有项目投资j万元最大投资回报值(0jn),用一维数组temp存储对到当前项目为止的所有项目投资j万元最大投资回报值(0jn)。为了输出投资方案,程序中使用了一个二维数组investinvest[k,j]记录了对前k个项目投资j万元获得最大投资回报时投资在第k个项目上的资金数。

[程序清单]

program ex8_3(input,output);

const maxm=100; maxn=100;

type arraytype=array [0..maxn] of real;

var i,j,k,m,n,rest:integer;

    q,maxreturn,temp:arraytype;

    invest:array[1..maxm,0..maxn] of integer;

    result:array[1..maxm] of integer;

    fname:string;

    f:text;

begin

     write('Input the name of datafile:');

     readln(fname);

     assign(f,fname);

     reset(f);

     readln(f,m,n);

     for j:=0 to n do read(f,q[j]);

     readln(f);

     for i:=1 to m do

         for j:=0 to n do invest[i,j]:=0;

     maxreturn:=q;

     for j:=0 to n do invest[1,j]:=j;

     for k:=2 to m do

     begin

          temp:=maxreturn;

          for j:=0 to n do invest[k,j]:=0;

          for j:=0 to n do read(f,q[j]);

          readln(f);

          for j:=0 to n do

              for i:=0 to j do

                  if maxreturn[j-i]+q[i]>temp[j] then

                     begin

                          temp[j]:=maxreturn[j-i]+q[i];

                          invest[k,j]:=i

                     end;

          maxreturn:=temp

     end;

     close(f);

     rest:=n;

     for i:=m downto 1 do

     begin

          result[i]:=invest[i,rest];

          rest:=rest-result[i]

     end;

     for i:=1 to m do write(result[i],' ');

     writeln(maxreturn[n]:0:2)

end.

 

[ 8-4] 花店橱窗布置问题(FLOWER)

(1)问题描述

    假设你想以最美观的方式布置花店的橱窗。现在你有F束不同品种的花束,同时你也有至少同样数量的花瓶被按顺序摆成一行。这些花瓶的位置固定于架子上,并从1V顺序编号,V是花瓶的数目,从左至右排列,则最左边的是花瓶1,最右边的是花瓶V。花束可以移动,并且每束花用1F间的整数唯一标识。标识花束的整数决定了花束在花瓶中的顺序,如果IJ,则令花束I必须放在花束J左边的花瓶中。

    例如,假设一束杜鹃花的标识数为1,一束秋海棠的标识数为2,一束康乃馨的标识数为3,所有的花束在放入花瓶时必须保持其标识数的顺序,即:杜鹃花必须放在秋海棠左边的花瓶中,秋海棠必须放在康乃馨左边的花瓶中。如果花瓶的数目大于花束的数目。则多余的花瓶必须空置,且每个花瓶中只能放一束花。

    每一个花瓶都具有各自的特点。因此,当各个花瓶中放入不同的花束时,会产生不同的美学效果,并以美学值(一个整数)来表示,空置花瓶的美学值为零。

在上述例子中,花瓶与花束的不同搭配所具有的美学值,如下表所示。

 

 

   

1

2

3

4

5

 

       

1 (杜鹃花)

 7

23

-5

-24

16

2 (秋海棠)

 5

21

-4

 10

23

3 (康乃馨)

-21

 5

-4

-20

20

 

     例如,根据上表,杜鹃花放在花瓶2中,会显得非常好看;但若放在花瓶4中则显得十分难看。

    为取得最佳美学效果,你必须在保持花束顺序的前提下,使花束的摆放取得最大的美学值。如果有不止一种的摆放方式具有最大的美学值,则其中任何一直摆放方式都可以接受,但你只要输出任意一种摆放方式。

    2)假设条件

l         1F100,其中F为花束的数量,花束编号从1F

l         FV100,其中V是花瓶的数量。

l         -50Aij50,其中Aij是花束i在花瓶j中的美学值。

 

3)输入

输入文件是正文文件(text file),文件名是flower.inp

l       第一行包含两个数:FV

l       随后的F行中,每行包含V个整数,Aij 即为输入文件中第(i+1 )行中的第j个数。

 

4)输出

输出文件必须是名为flower.out的正文文件,文件应包含两行:

第一行是程序所产生摆放方式的美学值。

第二行必须用F个数表示摆放方式,即该行的第K个数表示花束K所在的花瓶的编号。

 

5)例子

flower.inp:

 

3 5

7 23 –5 –24 16

5 21 -4 10 23

-21 5 -4 -20 20

 

 

 

 

 

 


 

flower.out:

53

2 4 5

 

 

 

 


 

    6)评分

    程序必须在2秒中内运行完毕。

在每个测试点中,完全正确者才能得分。

[算法设计] flower一题是IOI99第一天第一题,该题如用组合的方法处理,将会造成超时。正确的方法是用动态规划,考虑角度为一束一束地增加花束,假设用b(i,j)表示1i束花放在1j之间的花瓶中的最大美学值,其中i<=j ,则b(i,j)=max(b[i-1,k-1]+A[i,k]),其中i<=k<=jA(i,k)的含义参见题目。输出结果时,显然使得b[F,k]取得总的最大美观值的第一个k值就是第F束花应该摆放的花瓶位置,将总的最大美观值减去A[i,k]的值即得到前k-1束花放在前k-1个瓶中的最大美观值,依次使用同样的方法就可求出每一束花应该摆放的花瓶号。由于这一过程是倒推出来的,所以程序中用递归程序来实现。

[程序清单]

program ex8_4(input,output);

const max=100;

var f,v,i,j,k,cmax,current,max_val:integer;

    table,val:array[1..max,1..max] of integer;

    fname:string;

    fin:text;

procedure print(current,max_val:integer);

var i:integer;

begin

     if current>0 then

     begin

          i:=current;

          while val[current,i]<>max_val do i:=i+1;

          print(current-1,max_val-table[current,i]);

          write(i,' ')

     end

end;

begin

     write('Input the filename of test data:');

     readln(fname);

     assign(fin,fname);

     reset(fin);

     readln(fin,f,v);

     for i:=1 to f do

     begin

          for j:=1 to v do read(fin,table[i,j]);

          readln(fin);

     end;

     close(fin);

     max_val:=-maxint;

     for i:=1 to v do

         if max_val<table[1,i]

            then begin val[1,i]:=table[1,i];max_val:=table[1,i] end

            else val[1,i]:=table[1,i];

     for i:=2 to f do

         for j:=i to v-f+i do

         begin

              max_val:=-maxint;

              for k:=i-1 to j-1 do

              begin

                   cmax:=-maxint;

                   for current:=k+1 to j do

                       if table[i,current]>cmax then cmax:=table[i,current];

                   if cmax+val[i-1,k]>max_val then max_val:=cmax+val[i-1,k]

              end;

              val[i,j]:=max_val

         end;

     max_val:=-maxint;

     for i:=f to v do

         if val[f,i]>max_val then max_val:=val[f,i];

     writeln(max_val);

     print(f,max_val);

     writeln

end.

[运行结果]

参见flower子目录下的标准答案和测试数据。

3 5
7 23 –5 –24 16
5 21 -4 10 23
-21 5 -4 -20 20
53
2 4 5

 

 

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值