高中信息技术 全国青少年奥林匹克联赛教案 动态规划实例分析及程序实现.docx
《高中信息技术 全国青少年奥林匹克联赛教案 动态规划实例分析及程序实现.docx》由会员分享,可在线阅读,更多相关《高中信息技术 全国青少年奥林匹克联赛教案 动态规划实例分析及程序实现.docx(25页珍藏版)》请在冰豆网上搜索。
高中信息技术全国青少年奥林匹克联赛教案动态规划实例分析及程序实现
2019-2020年高中信息技术全国青少年奥林匹克联赛教案动态规划实例分析及程序实现
一、数字三角形
(图3.1-1)示出了一个数字三角形。
请编一个程序计算从顶至底的某处的一条路
径,使该路径所经过的数字的总和最大。
●每一步可沿左斜线向下或右斜线向下走;
●1<三角形行数≤100;
●三角形中的数字为整数0,1,…99;
输入数据:
由INPUT.TXT文件中首先读到的是三角形的行数。
在例子中INPUT.TXT表示如下:
5
7
38
810
2744
45265
输出数据:
把最大总和(整数)写入OUTPUT.TXT文件。
上例为:
30
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
(图3.1-1)
二、算法分析
只要对该题稍加分析,就可以得出一个结论:
如果得到一条由顶至底的某处的一条最佳路径,那么对于该路径上的每一个中间点来说,由顶至该中间点的路径所经过的数字和也为最大。
因此该题是一个典型的多阶段决策最优化
的问题。
我们采用动态规划中的顺推解法。
按三角形的行划分阶段。
若行数为n,则可把问题看作一个n-1个阶段的决策问题。
从始点出发,依顺向求出第一阶段、第二阶段,……,第n-1阶段中各决策点至始点的最佳路径,最终求出始点到终点的最佳路径。
设:
fk(Uk)━━从第k阶段中的点Uk至三角形顶点有一条最佳路径,该路径所经过的
数字的总和最大,fk(Uk)表示为这个数字和;
由于每一次决策有两个选择,或沿左斜线向下,或沿右斜线向下,因此设
Uk1━━k-1阶段中某点Uk沿左斜线向下的点;
Uk2━━k-1阶段中某点Uk沿右斜线向下的点;
dk(Uk1)━━k阶段中Uk1的数字;
dk(Uk2)━━k阶段中Uk2的数字;
因而可写出顺推关系式
fk(Uk)=max{fk-1(Uk)+dk(Uk1),fk-1(Uk)+dk(Uk2)}
f0(U0)=0;
K=1,2,3,4,……n
经过一次顺推,便可分别求出由顶至底N个数的N条路径,在这N条路径所经过的N个
数字和中,最大值即为正确答案。
三、程序分析
根据上述顺推关系,我们编写程序如下:
ProgramID1P1;
Const
Maxn=100;
Type
Node=Record
Val,Tot:
Integer
{当前格数字;从[1,1]到当前格的路径所经过的数字和}
End;
Var
List:
Array[1..Maxn,1..Maxn]ofNode;{计算表}
N,Max,{行数,最大总和}
I,J:
Integer;{辅助变量}
Fi:
Text;{文件变量}
ProcedureInit;
Begin
Assign(Fi,'INPUT.TXT');{文件名和文件变量连接}
Reset(Fi);{文件读准备}
Readln(Fi,N);{读三角形行数}
Fori:
=1toNDo{读入三角形各格的数字}
Forj:
=1toiDo
Read(Fi,List[i,j].Val);
Close(Fi)
End; {init}
ProcedureMain;
Begin
List[1,1].Tot:
=List[1,1].Val;{从[1,1]位置开始往下顺推}
Fori:
=2toNDo
Forj:
=1toiDoBegin
List[i,j].Tot:
=-1;{从[1,1]至[i,j]的数字和初始化}
If(j<>1)And
(List[i-1,j-1].Tot+List[i,j].Val>List[i,j].Tot)Then
List[i,j].Tot:
=List[i-1,j-1].Tot+List[i,j].Val;
{若从[i-1,j-1]选择右斜线向下会使[1,1]至[i,j]的数字和最大,则决策该步}
If(j<>i)And
(List[i-1,j].Tot+List[i,j].Val>List[i,j].Tot)Then
List[i,j].Tot:
=List[i-1,j].Tot+List[i,j].Val
{若从[i-1,j]选择左斜线向下会使[1,1]至[i,j]的数字和最大,则决策该步}
End;{for}
Max:
=1;
{[1,1]至底行各点的N条路径所经过的数字和中,选择最大的一个输出}
Fori:
=2toNDo
IfList[N,i].Tot>List[N,Max].TotThen
Max:
=i;
Writeln(List[N,Max].Tot){输出最大总和}
End;{main}
Begin
Init;{读入数字三角形}
Main{求最大总和}
End.{main}
二、Problem:
打鼹鼠
Contents:
有个n*n个格子,在m个时间点上的不定格子里有数量不等的鼹鼠出现,机器人每次只能向前后左右移动一个格子,问最多机器人能打多少鼹鼠?
(n<=1000,m<=10000)
Type:
动态规划
Difficulty:
2
Source:
HNOIxx_day_*_*
Solution:
a)记得学OI不到几个月,高一刚上来就做的这道题..着实郁闷了半天,有一个思路是开1000*1000的数组乱搞…忘了可以过几个来着..
b)又翻到这道题的时候是2月份了..发现f[i]表示:
如果机器人最后打死的老鼠是第i只,这种情况下机器人最多可以打死多少老鼠。
就可以了,然后我赫然发现10^8div2次的若干基本操作是要TLE的
c)鉴于这道题郁闷的理论时间复杂度无法优化,我请教了朱老师,原来动态规划枚举顺序也有其优化技巧,由于思路不是自己的,仅作简要介绍:
a)
(1).将更快、更容易“短路”的判断放在前面
b)
(2).将内部循环(j的循环)倒序,逼近最优解
d)我的计算机有点慢…
总分=100.0
第一题:
mole得分=100.0用时=7.16s
mole-0(mole1.In)结果=正确得分=10.0用时=0.01s空间=0.71M
mole-1(mole2.In)结果=正确得分=10.0用时=0.01s空间=0.71M
mole-2(mole3.In)结果=正确得分=10.0用时=0.02s空间=0.71M
mole-3(mole4.In)结果=正确得分=10.0用时=0.98s空间=0.71M
mole-4(mole5.In)结果=正确得分=10.0用时=1.02s空间=0.71M
mole-5(mole6.In)结果=正确得分=10.0用时=1.00s空间=0.71M
mole-6(mole7.In)结果=正确得分=10.0用时=1.05s空间=0.71M
mole-7(mole8.In)结果=正确得分=10.0用时=1.02s空间=0.71M
mole-8(mole9.In)结果=正确得分=10.0用时=1.02s空间=0.71M
mole-9(mole10.In)结果=正确得分=10.0用时=1.02s空间=0.71M
[分析]
设第i只老鼠在第Ti个时刻出现在(xi,yi),T1<=T2<=T3<=…<=Tm。
假设机器人打死了L只老鼠,不妨设这些老鼠的编号是a1,a2,…,aL。
显然对于任意的i(1<=i<=L-1)都有T[ai+1]-T[ai]>=|x[ai+1]-x[ai]|+|y[ai+1]-y[ai]|。
f[i]表示:
如果机器人最后打死的老鼠是第i只,这种情况下机器人最多可以打死多少老鼠。
可以列出方程:
f[i]=max{f[j]+1,1}
1<=j=|xi-xj|+|yi-yj|
最后求的答案就是max{f[i]}(1<=i<=m)
此算法时间复杂度为O(m2)。
考虑到m<=10000,而时限只有1second,所以还必须进行一些代码优化。
因为j
我们看看下面的代码:
fori:
=1tomdobegin
f[i]:
=0;
forj:
=1toi–1do
if(abs(x[i]-x[j])+abs(y[i]-y[j])<=T[i]-T[j])and(f[j]>f[i])then
f[i]:
=f[j];
f[i]:
=f[i]+1;
end;
它无疑是正确的。
但是循环中的判断语句大有文章可做:
上面的代码每次都铁定至少要执行3次减法、2次绝对值、1次比较运算。
这无疑是极度昂贵的操作代价。
所以我们可以将(f[j]>f[i])这个比较“便宜”的判断条件提到前面,变成如下形式:
if(f[j]>f[i])and(abs(x[i]-x[j])+abs(y[i]-y[j])<=T[i]-T[j])then
这样做的好处是一旦f[j]<=f[i]就可以执行“短路操作”(也就是说后面那一大截速度很慢的操作都可以避免。
不过编译的时候一定要记得设成{$B+})
实践证明速度是快了不少。
可是对于1second的时限还是不能胜任。
实战游戏经验告诉我们,机器人一般情况下不可能打完一只老鼠之后就跑到很远的地方去寻找新的猎物,肯定是一路上碰到一点老鼠就打一点。
所以机器人相继打的两只老鼠的出现时间不可能相差太远。
因此在方程
f[i]=max{f[j]+1,1}
1<=j=|xi-xj|+|yi-yj|
之中,使得i达到最优的j肯定不会和i差得太远。
同时在我们的判断语句中:
if(f[j]>f[i])and(abs(x[i]-x[j])+abs(y[i]-y[j])<=T[i]-T[j])then
f[i]越早接近最优值,判断语句的效率就越高(因为后面一截可以被短路掉)。
因此我们将循环语句改成这样的形式:
fori:
=1tomdobegin
f[i]:
=0;
forj:
=i–1downto1do
if(f[j]>f[i])and(abs(x[i]-x[j])+abs(y[i]-y[j])<=T[i]-T[j])then
f[i]:
=f[j];
f[i]:
=f[i]+1;
end;
实践发现,最慢的数据大概0.7s即可出解。
至此问题解决了。
Programmole;
{$B+}
varf,t,x,y:
array[1..10000]oflongint;
i,j,m,n,ans:
longint;
begin
assign(input,'mole.in');reset(input);
readln(n,m);
fori:
=1tomdoreadln(t[i],x[i],y[i]);
close(input);
fillchar(f,sizeof(f),0);
fori:
=1tomdo
begin
forj:
=i-1downto1do
if(f[j]>f[i])and(abs(x[i]-x[j])+abs(y[i]-y[j])<=t[i]-t[j])then
f[i]:
=f[j];
inc(f[i]);
iff[i]>ansthenans:
=f[i];
end;
assign(output,'mole.out');rewrite(output);
writeln(ans);
close(output);
END.
三、最大连续子序列
给出一个长度为n的整数序列A,找出i,j使得那一段连续数之和最大。
第一行为n第二行为数列
输入样例
6
3-524-16
输出样例
11
[分析]:
设Fi表示Ai为最后一个元素的最大连续子序列。
可得方程:
Fi=max{Fi-1,0}+Ai
时间复杂度为O(n)
Programzuidalianxuzixulie;
var
a,s:
array[0..10000]ofinteger;
i,j,n,max:
integer;
begin
max:
=0;
assign(input,'lxzxl.txt');
reset(input);
readln(n);
fillchar(s,sizeof(s),0);
fori:
=1tondo
begin
read(a[i]);
s[i]:
=s[i-1]+a[i];
end;
close(input);
fori:
=1tondo
forj:
=1tondo
ifs[j]-s[i]>maxthenmax:
=s[j]-s[i];
writeln(max);
end.
四、街道问题
在下图中找出从左下角到右上角的最短路径,每步只能向右方或上方走。
[分析]
这是一道简单而又典型的动态规划题,许多介绍动态规划的书与文章中都拿它来做例子。
通常,书上的解答是这样的:
按照图中的虚线来划分阶段,即阶段变量k表示走过的步数,而状态变量xk表示当前处于这一阶段上的哪一点。
这时的模型实际上已经转化成了一个特殊的多段图。
用决策变量uk=0表示向右走,uk=1表示向上走,则状态转移方程如下:
(这里的row是地图竖直方向的行数)
我们看到,这个状态转移方程需要根据k的取值分两种情况讨论,显得非常麻烦。
相应的,把它代入规划方程而付诸实现时,算法也很繁。
因而我们在实现时,一般是不会这么做的,而代之以下面方法:
(这里Distance表示相邻两点间的边长)
这样做确实要比上面的方法简单多了,但是它已经破坏了动态规划的本来面目,而不存在明确的阶段特征了。
如果说这种方法是以地图中的行(A、B、C、D)来划分阶段的话,那么它的"状态转移"就不全是在两个阶段之间进行的了。
也许这没什么大不了的,因为实践比理论更有说服力。
但是,如果我们把题目扩展一下:
在地图中找出从左下角到右上角的两条路径,两条路径中的任何一条边都不能重叠,并且要求两条路径的总长度最短。
这时,再用这种"简单"的方法就不太好办了。
如果非得套用这种方法的话,则最优指标函数就需要有四维的下标,并且难以处理两条路径"不能重叠"的问题。
而我们回到原先"标准"的动态规划法,就会发现这个问题很好解决,只需要加一维状态变量就成了。
即用xk=(ak,bk)分别表示两条路径走到阶段k时所处的位置,相应的,决策变量也增加一维,用uk=(xk,yk)分别表示两条路径的行走方向。
状态转移时将两条路径分别考虑
在写规划方程时,只要对两条路径走到同一个点的情况稍微处理一下,减少可选的决策个数:
从这个例子可以看出,合理地划分阶段和选择状态可以给解题带来方便。
六、花店橱窗
假设你想以最美观的方式布置花店的橱窗。
现在你有F束不同品种的花束,同时你也有至少同样数量的花瓶被按顺序摆成一行。
这些花瓶的位置固定于架子上,并从1至V顺序编号,V是花瓶的数目,从左至右排列,则最左边的是花瓶1,最右边的是花瓶V。
花束可以移动,并且每束花用1至F间的整数唯一标识。
标识花束的整数决定了花束在花瓶中的顺序,如果I<J,则令花束I必须放在花束J左边的花瓶中。
例如,假设一束杜鹃花的标识数为1,一束秋海棠的标识数为2,一束康乃馨的标识数为3,所有的花束在放入花瓶时必须保持其标识数的顺序,即:
杜鹃花必须放在秋海棠左边的花瓶中,秋海棠必须放在康乃馨左边的花瓶中。
如果花瓶的数目大于花束的数目。
则多余的花瓶必须空置,且每个花瓶中只能放一束花。
每一个花瓶都具有各自的特点。
因此,当各个花瓶中放入不同的花束时,会产生不同的美学效果,并以美学值(一个整数)来表示,空置花瓶的美学值为零。
在上述例子中,花瓶与花束的不同搭配所具有的美学值,如下表所示。
花瓶
12345
1(杜鹃花)723-5-2416
2(秋海棠)521-41023
3(康乃馨)-215-4-2020
例如,根据上表,杜鹃花放在花瓶2中,会显得非常好看;但若放在花瓶4中则显得十分难看。
为取得最佳美学效果,你必须在保持花束顺序的前提下,使花束的摆放取得最大的美学值。
如果有不止一种的摆放方式具有最大的美学值,则其中任何一直摆放方式都可以接受,但你只要输出任意一种摆放方式。
(2)假设条件
1≤F≤100,其中F为花束的数量,花束编号从1至F。
F≤V≤100,其中V是花瓶的数量。
-50≤Aij≤50,其中Aij是花束i在花瓶j中的美学值。
(3)输入
第一行包含两个数:
F,V。
随后的F行中,每行包含V个整数,Aij即为输入文件中第(i+1)行中的第j个数。
(4)输出
一行是程序所产生摆放方式的美学值。
【样例输入1】【样例输入2】
35
723-5-2416
521-41023
-215-4-2020
【样例输出1】【样例输出2】
53
本题虽然是IOI’99中较为简单的一题,但其中大有文章可作。
说它简单,是因为它有序,因此我们一眼便可看出这题应该用动态规划来解决。
但是,如何动态规划呢?
如何划分阶段,又如何选择状态呢?
<方法1>
以花束的编号来划分阶段。
在这里,第k阶段布置第k束花,共有F束花,有F+1个阶段,增加第F+1阶段是为了计算的方便;状态变量xk表示第k束花所在的花瓶。
而对于每一个状态xk,决策uk就是第k+1束花放置的花瓶号;最优指标函数fk(xk)表示从第k束花到第n束花所得到的最大美学值;A(i,j)是花束i插在花瓶j中的美学值,V是花瓶总数,F是花的总数。
状态转移方程为
规划方程为
边界条件为:
事实上这是一个虚拟的边界。
最后要求的最大美学价值是
<方法2>
方法1的规划方程中的允许决策空间:
xk+1≤uk≤V-(F-k)+1比较麻烦,因此有待改进。
还是以花束的编号来划分阶段,第k阶段布置第k束花;状态变量xk表示第k束花所在的花瓶;注意,这里我们考虑倒过来布置花瓶,即从第F束花开始布置到第1束花。
于是状态变量uk表示第k-1束花所在的花瓶;最优指标fk(xk)表示从第一束花到第k束花所获得的美学价值;A(i,j)是花束i插在花瓶j中的美学值,V是花瓶总数,F是花的总数。
则状态转移方程为:
规划方程为:
增加的虚拟边界条件为:
最后要求的最大美学价值是:
可以看出,这种方法实质上和方法1没有区别,但是允许决策空间的表示变得简单了。
<方法3>
以花瓶的数目来划分阶段,第k个阶段决定花瓶k中是否放花;状态变量xk表示前k个花瓶中放了多少花;而对于任意一个状态xk,决策就是第xk束花是否放在第k个花瓶中,用变量uk=1或0来表示。
最优指标函数fk(xk)表示前k个花瓶中插了xk束花,所能取得的最大美学值。
注意,这里仍然是倒过来考虑。
状态转移方程为
规划方程为
边界条件为
三种不同的方法都成功地解决了问题,只不过因为阶段的划分不同,状态的表示不同,决策的选择有多有少,所以算法的时间复杂度也就不同。
这个例子具有很大的普遍性。
有很多的多阶段决策问题都有着不止一种的阶段划分方法,因而往往就有不止一种的规划方法。
有时各种方法所产生的效果是差不多的,但更多的时候,就像我们的例子一样,两种方法会在某个方面有些区别。
所以,在用动态规划解题的时候,可以多想一想是否有其它的解法。
对于不同的解法,要注意比较,好的算法好在哪里,差一点的算法差在哪里。
从各种不同算法的比较中,我们可以更深刻地领会动态规划的构思技巧。
七、航线设置
问题描述:
美丽的莱茵河畔,每边都分布着N个城市,两边的城市都是唯一对应的友好城市,现需要在友好城市开通航线以加强往来.但因为莱茵河常年大雾,如果开设的航线发生交叉现象就有可能出现碰船的现象.现在要求近可能多地开通航线并且使航线不能相交!
假如你是一个才华横溢的设计师,该如何设置友好城市间的航线使的航线数又最大又不相交呢?
分析:
此问题可以演化成求最大不下降序列来完成.源程序如下:
programdongtai;{动态规划之友好城市航线设置问题}
var
d:
array[1..1000,1..4]ofinteger;
i,j,k,n,L,p:
integer;
procedureprint(L:
integer);{打印结果}
begin
writeLn('最多可设置的航线数是:
',k);
repeat
writeLn(d[L,1]:
4,d[L,2]:
4);{输出可以设置航线的友好城市代码}
L:
=d[L,4]
untiLL=0
end;
begin
writeLn('输入友好城市对数:
');
readLn(n);
writeLn('输入友好城市对(友好城市放在同一行:
');{输入}
fori:
=1tondo
readLn(d[i,1],d[i,2]);{D[I,1]表示起点,D[I,2]表示终点}
fori:
=1tondo
begin
d[i,3]:
=1;{D[I,3]表示可以设置的航线条数}
d[i,4]:
=0{D[I,4]表示后继,即下一条航线从哪里开始设置,为0表示不能设置下一条航线}
end;
fori:
=n-1downto1do{从倒数第二个城市开始规划}
begin
L:
=0;p:
=0;{L表示本城市后面可以设置的航线数,P表示下条航线从哪个城市开始}
forj:
=i+1tondo{找出本城市后面可以设置的最大航线数和小条航线到底从哪个城市开始设置}
if(d[i,2]L)then
{如果本城市I的终点小于后面城市的终点(即不相交)}{并且此城市后面可以设置的航线数大于L}
begin
L:
=d[j,3];{那么L等于城市J的可以设置航线数}
p:
=j{P等于可以设置下条航线的城市代码}
end;
ifL>0then{如果本城市后面总共可以设置的航线数>0则}
begin
d[i