多阶段决策过程
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
动态规划
多阶段决策过程(multistep decision process )是指这样一类特殊的活动过程,过程可以按时间顺序分解成若干个相互联系的阶段,在每一个阶段都需要做出决策,全部过程的决策是一个决策序列。
动态规划(dynamic programming )算法是解决多阶段决策过程最优化问题的一种常用方法,难度比较大,技巧性也很强。
利用动态规划算法,可以优雅而高效地解决很多贪婪算法或分治算法不能解决的问题。
动态规划算法的基本思想是:将待求解的问题分解成若干个相互联系的子问题,先求解子问题,然后从这些子问题的解得到原问题的解;对于重复出现的子问题,只在第一次遇到的时候对它进行求解,并把答案保存起来,让以后再次遇到时直接引用答案,不必重新求解。
动态规划算法将问题的解决方案视为一系列决策的结果,与贪婪算法不同的是,在贪婪算法中,每采用一次贪婪准则,便做出一个不可撤回的决策;而在动态规划算法中,还要考察每个最优决策序列中是否包含一个最优决策子序列,即问题是否具有最优子结构性质。
动态规划算法的有效性依赖于待求解问题本身具有的两个重要性质:最优子结构性质和子问题重叠性质。
1 、最优子结构性质。
如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。
最优子结构性质为动态规划算法解决问题提供了重要线索。
2 、子问题重叠性质。
子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。
动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的解题效率。
当我们已经确定待解决的问题需要用动态规划算法求解时,通常可以按照以下步骤设计动态规划算法:
1 、分析问题的最优解,找出最优解的性质,并刻画其结构特征;
2 、递归地定义最优值;
3 、采用自底向上的方式计算问题的最优值;
4 、根据计算最优值时得到的信息,构造最优解。
1 ~3 步是动态规划算法解决问题的基本步骤,在只需要计算最优值的问题中,完成这三个基本步骤就可以了。
如果问题需要构造最优解,还要执行第 4 步;此时,在第3 步通常需要记录更多的信息,以便在步骤4 中,有足够的信息快速地构造出最优解。
下面通过对具体实例的分析,帮助大家领会可用动态规划算法求解的问题应具有的两个性质以及动态规划算法的设计思想。
例:拦截导弹(问题来源:1999 年全国青少年信息学(计算机)奥林匹克分区联赛高中组复赛试题第一题)
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。
但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。
某天,雷达捕捉到敌国的导弹来袭。
由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000 的正整数),计算这套系统最多能拦截多少导弹,并依次输出被拦截的导弹飞来时候的高度。
样例:
389 207 155 300 299 170 158 65
OUTPUT
6 (最多能拦截的导弹数)
389 300 299 170 158 65
分析:因为只有一套导弹拦截系统,并且这套系统除了第一发炮弹能到达任意高度外,以后的每一发炮弹都不能高于前一发炮弹的高度;所以,被拦截的导弹应该按飞来的高度组成一个非递增序列。
题目要求我们计算这套系统最多能拦截的导弹数,并依次输出被拦截导弹的高度,实际上就是要求我们在导弹依次飞来的高度序列中寻找一个最长非递增子序列。
设X={x 1 ,x 2 ,…,x n } 为依次飞来的导弹序列,Y={y 1 ,y 2 ,…,y k } 为问题的最优解(即X 的最长非递增子序列),s 为问题的状态(表示导弹拦截系统当前发送炮弹能够到达的最大高度,初值为s=∞——第一发炮弹能够到达任意的高度)。
如果y 1 =x 1 ,即飞来的第一枚导弹被成功拦截。
那么,根据题意“每一发炮弹都不能高于前一发的高度”,问题的状态将由s=∞变成s≤x 1 (x 1 为第一枚导弹的高度);在当前状态下,序列Y 1 ={y 2 ,…,y k } 也应该是序列X 1 ={x 2 ,…,x n } 的最长非递增子序列(大家用反证法很容易证明)。
也就是说,在当前状态s≤x 1 下,问题的最优解Y 所包含的子问题(序列X 1 )的解(序列Y 1 )也是最优的。
这就是拦截导弹问题的最优子结构性质。
设D(i) 为第i 枚导弹被拦截之后,这套系统最多还能拦截的导弹数(包含被拦截的第i 枚)。
我们可以设想,当系统拦截了第k 枚导弹x k ,而x k 又是序列X={x 1 ,x 2 ,…,x n } 中的最小值,即第k 枚导弹为所有飞来的导弹中高度最低的,则有D(k)=1 ;当系统拦截了最后一枚导弹x n ,那么,系统最多也只能拦截这一枚导弹了,即D(n)=1 ;其它情况下,也应该有D(i)≥1 。
根据以上分析,可归纳出问题的动态规划递归方程为:
假设系统最多能拦截的导弹数为dmax (即问题的最优值),则
dmax (i 为被系统拦截的第一枚导弹的顺序号)
所以,要计算问题的最优值dmax ,需要分别计算出D(1) 、D(2) 、……D(n) 的值,然后将它们进行比较,找出其中的最大值。
根据上面分析出来的递归方程,我们完全可以设计一个递归函数,采用自顶向下的方法计算D(i) 的值。
然后,对i 从 1 到n 分别调用这个递归函数,就可以计算出D(1) 、D(2) 、……D(n) 。
但这样将会有大量的子问题被重复计算。
比如在调用递归函数计算D(1) 的时候,可能需要先计算D(5) 的值;之后在分别调用递归函数计算D(2) 、D(3) 、D(4) 的时候,都有可能需要先计算D(5) 的值。
如此一来,在整个问题的求解过程中,D(5) 可能会被重复计算很多次,从而造成了冗余,降低了程序的效率。
其实,通过以上分析,我们已经知道:D(n)=1 。
如果将n 作为阶段对问题进行划分,根据问题的动态规划递归方程,我们可以采用自底向上的方法依次计算出D(n-1) 、D(n-2) 、……D(1) 的值。
这样,每个D(i) 的值只计算一次,并在计算的同时把计算结果保存下来,从而避免了有些子问题被重复计算的情况发生,提高了程序的效率。
算法代码如下:
for i=1 to n
D(i)=1
next i
for i=n-1 to 1 step -1
for j=i+1 to n
if x(j)<=x(i) and D(i)<D(j)+1 then
D(i)=D(j)+1
next j
next i
dmax=0:xh=1
rem dmax 用来保存问题的最优值(系统最多能拦截的导弹数);xh 用来保存第一枚被拦截的导弹的顺序号,以便后面构造最优解
for i=1 to n
if D(i)>dmax then
dmax=D(i)
xh=i
endif
next i
我们在计算最优值时保存了被拦截的第一枚导弹的顺序号xh 。
接下来通过这个信息构造问题的最优解(由所有被拦截的导弹的高度组成的非递增序列)。
算法代码如下:
print x(xh)
for i=xh+1 to n
if x(i)<=x(i-1) then
print x(i)
endif
next i
关健字:阶段状态决策函数递推式
摘要:
动态规划是解决多阶段决策最优化问题的一种思想方法。
所谓“动态”,指的是在问题的多阶段决策中,按某一顺序,根据每一步所选决策的不同,将随即引起状态的转移,最终在变化的状态中产生一个决策序列。
动态规划就是为了使产生的决策序列在符合某种条件下达到最优。
动态规划思想近来在各类型信息学竞赛中频繁出现,它的应用也越来越受人重视。
本文就是讨论如何运用动态规划的思想设计出有效的数学模型来解决问题。
一动态规划问题的数学描述
我们先来看一个简单的多阶段决策问题。
[例1]现有一张地图,各结点代表城市,两结点间连线代表道路,线上数字表示城市间的距离。
如图1所示,试找出从结点1到结点10的最短路径。
第一阶段第二阶段第三阶段第四阶段第五阶段
图1
本问题的解决可采用一般的穷举法,即把从结点1至结点10的所有道路列举出来,计算其长度,再进行比较,找出最小的一条。
虽然问题能解决,但采用这种方法,当结点数增加,其运算量将成指数级增长,故而效率是很低的。
分析图1可知,各结点的排列特征:
(1) 可将各结点分为5个阶段;
(2) 每个阶段上的结点只跟相邻阶段的结点相连,不会出现跨阶段或同阶段结点相连的情况,如不会出现结点1与结点4连、结点4与结点5连的情况。
(3) 除起点1和终点10外,其它各阶段的结点既是上一阶段的终点,又是下一阶段的起点。
例如第三阶段的结点4、5、6,它即是上一阶段结点2、3中某结点的终点,又是下一阶段结点7、8、9中某结点的起点。
根据如上特征,若对于第三阶段的结点5,选择1-2-5和1-3-5这两条路径,后者的费用要小于前者。
那么考虑一下,假设在所求的结点1到结点10最短路径中要经过结点5,那我们在结点1到结点5之间会取那条路径呢?显然,无论从结点5出发以后的走法如何,前面选择1-3-5这条路都总是会优于1-2-5的。
也就是说,当某阶段结点一定时,后面各阶段路线的发展不受这点以前各阶段的影响。
反之,到该点的最优决策也不受该点以后的发展影响。
由此,我们可以把原题所求分割成几个小问题,从阶段1开始,往后依次求出结点1到阶段2、3、4、5各结点的最短距离,最终得出答案。
在计算过程中,到某阶段上一结点的决策,只依赖于上一阶段的计算结果,与其它无关。
例如,已求得从结点1到结点5的最优值是6,到结点6的最优值是5,那么要求到下一阶段的结点8的最优值,只须比较min{6+5,5+5}即可。
这样,运用动态规划思想大大节省了计算量。
可以看出,动态规划是解决此类多阶段决策问题的一种有效方法。
二动态规划中的主要概念,名词术语
1阶段:把问题分成几个相互联系的有顺序的几个环节,这些环节即称为阶段。
2 状态:某一阶段的出发位置称为状态。
通常一个阶段包含若干状态。
如图1中,阶段3就有三个状态结点4、5、6。
3 决策:从某阶段的一个状态演变到下一个阶段某状态的选择。
4策略:由开始到终点的全过程中,由每段决策组成的决策序列称为全过程策略,简称策略。
5 状态转移方程:前一阶段的终点就是后一阶段的起点,前一阶段的决策选择导出了后一阶段的状态,这种关系描述了由k阶段到k+1阶段状态的演变规律,称为状态转移方程。
6 目标函数与最优化概念:目标函数是衡量多阶段决策过程优劣的准则。
最优化概念是在一定条件下找到一个途径,经过按题目具体性质所确定的运算以后,使全过程的总效益达到最优。
三运用动态规划需符合的条件
任何思想方法都有一定的局限性,超出了特定条件,它就失去了作用。
同理,动态规划也并不是万能的。
那么使用动态规划必须符合什么条件呢?必须满足最优化原理和无后效性。
1 最优化原理
最优化原理可这样阐述:一个最优化策略具有这样的性质,不论过去状
图2
态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。
简而言之,一个最优化策略的子策略总是最优的。
如图2中,若路线I和J是A到C的最优路径,则根据最优化原理,路线J必是从B 到C的最优路线。
这可用反证法证明:假设有另一路径J’是B到C的最优路径,则A到C的路线取I和J’比I和J更优,这与原名题矛盾。
从而证明J’必是B到C的最优路径。
最优化原理是动态规划的基础,任何问题,如果失去了最优化原理的支持,就不可能用动态规划方法计算。
2 无后效性
“过去的步骤只能通过当前状态影响未来的发展,当前的状态是历史的总结”。
这条特征说明动态规划只适用于解决当前决策与过去状态无关的问题。
状态,出现在策略任何一个位置,它的地位相同,都可实施同样策略,这就是无后效性的内涵。
由上可知,最优化原理,无后效性,是动态规划必须符合的两个条件。
四动态规划的计算方法
对于一道题,怎样具体运用动态规划方法呢?
(1)首先,分析题意,考察此题是否满足最优化原理与无后效性两个条件。
(2)接着,确定题中的阶段,状态,及约束条件。
(3)推导出各阶段状态间的函数基本方程,进行计算。
具体求解则有多种方法。
1 前向与后向动态规划法
所谓前向与后向,指的是从起点出发,层层递推,直到终点,或从终点出发,逆向求解。
这两种方法本质上是一样的,具体解题时,可根据实际情况来选用。
[例2] 排队买票
问题描述:一场演唱会即将举行。
现有N(O〈N〈=200)个歌迷排队买票,一个人买一张,而售票处规定,一个人每次最多只能买两张票。
假设第I位歌迷买一张票需要时间Ti(1〈=I〈=n),队伍中相邻的两位歌迷(第j个人和第j+1个人)也可以由其中一个人买两张票,而另一位就可以不用排队了,则这两位歌迷买两张票的时间变为Rj,假如Rj 〈Tj+Tj+1,则这样做就可以缩短后面歌迷等待的时间,加快整个售票的进程。
现给出N,Tj和Rj,求使每个人都买到票的最短时间和方法。
解决问题:本题的阶段十分明显,只要按排队的先后顺序划分即可。
而买票的方式只有两种,要么一人买一张,要么一人买两张,整个过程呈线性排列。
要使前I个人买票时间最短,只需考虑前I个人的买票方式,与队列以后的人无关。
而且显而易见,在最优策略中,任意m 个连续的决策也肯定是最优的。
这样,问题就符合了最优化原理及无后效性,能运用动态规划。
那如何构造函数递推式呢?可以以到每个人为止所需的最短时间为状态值,设为f(i),于是有f(i)=min{f(i-1)+Ti,f(i-2)+Ri-1},起步时f(0)=0,f(1)=T1 。
如此从前往后,只需遍历一次即可。
上面的分析是从前往后进行的。
其实倒过来逆推也一样。
设f(I)表示当票卖到第I个人时,最少还需多少时间才能卖完。
则函数递推式为f(I)=min{f(I+1)+Ti,f(I+2)+Ri},从后往前逆推,起步时f(n)=Tn,f(n+1)=0 。
采用前向还是后向动态规划,要看实际情况而定,哪一种直观、简便,就运用哪一种。
2 具有隐含阶段的问题(即阶段不明显)
动态规划的一个重要环节是阶段划分,可有些题目无明显阶段,但也符合最优化原理,怎么解决呢?下面来看一道例题。
[例3] 最小费用问题
问题描述:已知从A到J的路线及费用如图3,求从A到J的最小费用路线。
图3
解决问题:本问题没有明显的阶段划分,各点间没有一定的先后次序,不能按照最少步数来决定顺序,如从A到D走捷径需4,但A-C-D只需3,更优。
看来图中出现回路,不能实施动态规划。
其实不然。
细想一下,从A到J的最优策略,它每一部分也是最优的(可以用前述的反证法来证明),换言之,本题也具有最优化性质,只是阶段不明显而已。
对于这类问题,我们可以换个角度分析,构造算法。
比较一下前面所讲的前向与后向动态规划法,都是以某个状态为终点,寻找到达次点的路径,然后比较优劣,确定此状态最优值。
可是,本题阶段不明显,各状态之间的道路会出现嵌套,故此法不能使用。
变一下角度,每次都以某个状态为起点,遍历由它引申出去的路径,等所有已知状态都扩展完了,再来比较
所有新状态,把值最小的那个状态确定下来,其它的不动。
如图3,先从A出发,找到3个结点B,D,C,费用为F(B)=3,F(D)=4,F(C)=2。
因为F(B),F(D)都大于F(C),那么可以确定:不可能再有路线从B或D出发到C,比A-C更优。
这样F(C)的最优值便确定了。
可是,有没有路线从C出发到B或D,比A-B或A-D更优呢?还不清楚。
继续下去,因为A扩展完了,只有从C开始,得到A-C-D=3,A-C-F=3,于是F(D)的值被刷新了,等于3。
现在,有F(B)=F(D)=F(F)=3,于是,三点的最优值都确定下来了。
然后以分别以三个点为起点,继续找。
以次类推,直到J点的最优值确定为止。
细心观察,其实本题的隐含阶段就是以各结点的最优值的大小来划分的,上述过程就是按最优值从小到大前向动态规划。
人们习惯上把此题归入到图论范畴中,并将上述方法称为标号法。
五动态规划的应用
上文叙述了动态规划的几种解法,下面我们通过例题来加深了解。
[例4] 复制书稿(BOOKS)
问题描述:假设有M本书(编号为1,2,…M),想将每本复制一份,M本书的页数可能不同(分别是P1,P2,…PM)。
任务时将这M本书分给K个抄写员(K〈=M〉,每本书只能分配给一个抄写员进行复制,而每个抄写员所分配到的书必须是连续顺序的。
意思是说,存在一个连续升序数列0=bo〈b1〈b2〈…<bk-1 <bk=m,这样,第I号抄写员得到的书稿是从bi-1+1到第bi本书。
复制工作是同时开始进行的,并且每个抄写员复制的速度都是一样的。
所以,复制完所有书稿所需时间取决于分配得到最多工作的那个抄写员的复制时间。
试找一个最优分配方案,使分配给每一个抄写员的页数的最大值尽可能小(如存在多个最优方案,只输出其中一种)。
解决问题:该题中M本书是顺序排列的,K个抄写员选择数也是顺序且连续的。
不管以书的编号,还是以抄写员标号作为参变量划分阶段,都符合策略的最优化原理和无后效性。
考虑到K〈=M,以抄写员编号来划分会方便些。
设F(I,J)为前I个抄写员复制前J本书的最小“页数最大数”。
于是便有F(I,J)=MIN{ F (I-1,V),T(V+1,J)} (1〈=I〈=K,I〈=J〈=M-K+I,I-1〈=V〈=J-1〉。
其中T(V+1,J)表示从第V+1本书到第J本书的页数和。
起步时F(1,1)=P1。
观察函数递推式,发现F(I)阶段只依赖于F(I-1)阶段的状态值,编程时可令数组F的范围为(0…1,1…M),便于缩小空间复杂度。
[例5] 多米诺骨牌(DOMINO)
问题描述:有一种多米诺骨牌是平面的,其正面被分成上下两部分,每一部分的表面或者为空,或者被标上1至6个点。
现有一行排列在桌面上:
顶行骨牌的点数之和为6+1+1+1=9;底行骨牌点数之和为1+5+3+2=11。
顶行和底行的差值是2。
这个差值是两行点数之和的差的绝对值。
每个多米诺骨牌都可以上下倒置转换,即上部变为下部,下部变为上部。
现在的任务是,以最少的翻转次数,使得顶行和底行之间的差值最小。
对于上面这个例子,我们只需翻转最后一个骨牌,就可以使得顶行和底行的差值为0,所以例子的答案为1。
解决问题:例子的上下部分之差是6+1+1+1-(1+5+3+2)=(6-1)+(1-5)+(1-3)+(1-2)=-2,而翻转最后一个骨牌后,上下之差变为(6-1)+(1-5)+(1-3)+(2-1)=0。
由此看出,一个骨牌对翻转策略造成影响的是上下两数之差,骨牌上的数则是次要的了。
这么一来,便把骨牌的放置状态由8个数字变为4个: 5 -4 -2 -1,翻转时只需取该位数字的相反数就行了。
在本题中,因为各骨牌的翻转顺序没有限定,所以不能按骨牌编号作为阶段来划分。
怎么办
呢?考虑到隐含阶段类型的问题可以按状态最优值的大小来划分阶段。
于是,我们以骨牌序列上下两部分的差值I作为状态,把达到这一状态的翻转步数作为状态值,记为f(I)。
便有f(I)=min{f(I+j)+1} (-12〈=j<=12,j为偶数,且要求当前状态有差值为j/2的骨牌)。
这里,I不是无限增大或减小,其范围取决于初始骨牌序列的数字差的和的大小。
具体动态规划时,如例题,我们以f(-2)=0起步,根据骨牌状态,进行一次翻转,可得到f(-12)=1,f(6)=1,f(2)=1,f(0)=1,由于出现了f(0),因此程序便可以结束,否则将根据四个新状态继续扩展,直至出现f(0)或者无法生成新状态为止。
注意:在各状态,除记录最少步数外,还需记录到达这一状态时各骨牌的放置情况;而当到达某一状态发现已记录有一种翻转策略时,则取步数较小的一种。
六小结
动态规划是一种高效算法。
若能成功运用,必会使程序效率,无论时间还是空间,都有着质的飞跃。
因此,我们视其为算法思想中的一项重要内容,有熟练掌握,深入研究的必要。
希望本文能给大家一点启发。
[附录]
例4:
输入格式:
文件的第一行是两个整数m和k (1〈=k〈=m〈=500)。
第二行有m个整数P1,P2,…,Pm,这m个整数均为正整数且都不超过1000000。
每两个整数之间用空格分开。
输出格式:
文件有k行,每行有两个正整数。
整数之间用空格分开。
第I行的两个整数ai和bi,表示第I号抄写员所分配得到的书稿的起始编号与终止编号。
程序如下:
program books;
type tp=array[1..500] of integer;
tc=array[1..500] of longint;
var c:array[1..500] of ^tp;
{记录路径}
f:array[0..1] of tc;
{状态值}
t:tc;
{书本页数和}
cc:tp;
{链接路径}
i,j,v,k,m,x,y,min,p1,p2:longint;
f1,f2:text;
procedure init;
{输入部分}
begin
assign(f1,'books.dat');
reset(f1);
assign(f2,'books.out');
rewrite(f2);
readln(f1,m,k);
if k=1 then
begin
writeln(f2,1,' ',m);
close(f2);
halt;
end;
{当k=1时,作特殊处理}
for i:=1 to m do
begin
read(f1,j);
if i=1 then t[1]:=j else
t[i]:=t[i-1]+j;
{累加页数}
end;
for i:=1 to k do
new(c[i]);
end;
procedure main;
{主过程}
begin
p1:=1;f[1]:=t;
{起步状态}
for i:=2 to k-1 do
{利用函数递推式计算}
begin
p2:=p1;p1:=1-p2;
for j:=i to m-k+i do
begin
min:=maxlongint;y:=0;
for v:=i-1 to j-1 do
begin
if f[p2,v]>t[j]-t[v] then x:=f[p2,v] else x:=t[j]-t[v];
if x<min then begin min:=x;y:=v;end;
end;
f[p1,j]:=min;
c[i]^[j]:=y;
end;
end;
p2:=p1;p1:=1-p2;
min:=maxlongint;y:=0;
for i:=k-1 to m-1 do
begin
if f[p2,i]>t[m]-t[i] then x:=f[p2,i] else x:=t[m]-t[i];
if x<min then begin min:=x;y:=i;end;
end;
{最后找出最优分配方案}
for i:=k-1 downto 1 do
begin
cc[i]:=y;
y:=c[i]^[y];
end;
{链接路径}
writeln(f2,1,' ',cc[1]);
for j:=2 to k-1 do
writeln(f2,cc[j-1]+1,' ',cc[j]);
writeln(f2,cc[k-1]+1,' ',m);
close(f2);
{打印}
end;
begin
init;
main;
end.
例5:
输入格式:
文件的第一行是一个整数n(1〈=n〈=1000〉,表示有n个多米诺骨牌在桌面上排成一行。
接下来共有n行,每行包含两个整数a、b(0〈=a、b〈=6,中间用空格分开〉。
第I+1行的a、b分别表示第I个多米诺骨牌的上部与下部的点数(0表示空)。
输出格式:
只有一个整数在文件的第一行。
这个整数表示翻动骨牌的最少次数,从而使得顶行和底行的差值最小。
程序如下:
program domino;
type tp=array[1..6] of integer;
var t:array[1..6000] of ^tp;
{记录骨牌摆放状态}
f:array[-6000..6000] of integer;
{记录达到某个差值的最少步数}
l:array[1..6000] of integer;
{扩展队列}
tt:tp;
i,j,n,m,x,y,ft,re:integer;
f1,f2:text;
procedure init;
{程序初始化}
begin
assign(f1,'domino.dat');
reset(f1);
assign(f2,'domino.out');
rewrite(f2);
m:=0;
ft:=0;re:=1;new(t[1]);
fillchar(t[1]^,sizeof(t[1]^),0);
fillchar(f,sizeof(f),0);
fillchar(tt,sizeof(tt),0);
readln(f1,n);
for i:=1 to n do
begin
readln(f1,x,y);
if x<>y then
begin
x:=x-y;
inc(m,x);
inc(tt[abs(x)]);
if x>0 then inc(t[1]^[x]);
end;
end;
if m=0 then
begin
writeln(f2,0);
close(f2);
halt;
end;
{处理步数为零的情况}
l[1]:=m;
f[m]:=1;
end;
procedure main;
{主过程}
begin
repeat
for ft:=ft+1 to re do
{以步数为阶段扩展状态}
begin
x:=l[ft];
for i:=1 to 6 do
{不同差值的六种情况}
begin
if x<6 then
if (t[ft]^[i]<tt[i])and(f[x+i*2]=0) then
begin
inc(re);l[re]:=x+i*2;
f[l[re]]:=f[x]+1;
if l[re]=0 then
{找到解便打印}
begin
writeln(f2,f[l[re]]-1);
close(f2);
halt;
end;
new(t[re]);
t[re]^:=t[ft]^;
inc(t[re]^[i]);
end;
{差值增加}
if x>-6 then
if (t[ft]^[i]>0)and(f[x-i*2]=0) then
begin
inc(re);l[re]:=x-i*2;
f[l[re]]:=f[x]+1;
if l[re]=0 then
{找到解便打印}
begin
writeln(f2,f[l[re]]-1);
close(f2);
halt;
end;
new(t[re]);
t[re]^:=t[ft]^;
dec(t[re]^[i]);
end;
{差值减少}
end;
end;
until ft=re;
for i:=1 to 6 do
if (f[i]>0)or(f[-i]>0) then
begin
if (f[-i]>0)and((f[i]=0)or(f[-i]<f[i])) then x:=f[-i] else x:=f[i];
writeln(f2,x-1);
i:=6;
end;
close(f2);
{骨牌上下两行点数之和的绝对值不为零}
一.试题
在一个园形操场的四周摆放N堆石子(N≤100),现要将石子有次序地合并成一堆。
规定
每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
编一程序,由文件读入堆数N及每堆的石子数(≤20),
①选择一种合并石子的方案,使得做N-1次合并,得分的总和最小;
②选择一种合并石子的方案,使得做N-1次合并,得分的总和最大。
例如,所示的4堆石子,每堆石子数(从最上面的一堆数起,顺时针数)依
次为4594。
则3次合并得分总和最小的方案:8+13+22=43
得分最大的方案为:14+18+22=54
输入数据:
文件名由键盘输入,该文件内容为:
第一行为石子堆数N;
第二行为每堆的石子数,每两个数之间用一个空格符分隔。
输出数据:
输出文件名为output.txt
从第1至第N行为得分最小的合并方案。
第N+1行是空行。
从第N+2行到第2N+1行是得
分最大合并方案。
每种合并方案用N行表示,其中第i行(1≤i≤N)表示第i 次合并前各堆的石子数(依顺时针次序输出,哪一堆先输出均可)。
要求将待合并的两堆石子数以相应的负数表示,以便标识。
输入输出范例:
输入文件内容:
4
4594
输出文件内容:
-459-4
-8-59
-13-9
22
4-5-94
4-14-4
-4-18
22
二.算法分析
竞赛中多数选手都不约而同地采用了尽可能逼近目标的贪心法来逐次合并:从最上面的一堆开始,沿顺时针方向排成一个序列。
第一次选得分最小(最大)的相邻两堆合并,
形成新的一堆;接下来,在N-1堆中选得分最小(最大)的相邻两堆合并……,依次类推,
直至所有石子经N-1次合并后形成一堆。
例如有6堆石子,每堆石子数(从最上面一堆数起,顺时针数)依次为346542
要求选择一种合并石子的方案,使得做5次合并,得分的总和最小。
按照贪心法,合并的过程如下:
每次合并得分
第一次合并346542->5
第二次合并54654->9
第三次合并9654->9
第四次合并969->15
第五次合并159->24
24
总得分=5+9+9+15+24=62。