石子合并问题报告.docx
《石子合并问题报告.docx》由会员分享,可在线阅读,更多相关《石子合并问题报告.docx(14页珍藏版)》请在冰豆网上搜索。
石子合并问题报告
石子合并(动态规划)详细解题报告2007-02-2514:
58
.试题
在一个园形操场的四周摆放N堆石子(NW100),现要将石子有次序地合并成一堆。
规定
每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
编一程序,由文件读入堆数N及每堆的石子数(<20),
①选择一种合并石子的方案,使得做N-1次合并,得分的总和最小;
②选择一种合并石子的方案,使得做N-1次合并,得分的总和最大。
例如,所示的4堆石子,每堆石子数(从最上面的一堆数起,顺时针数)依
次为4594。
则3次合并得分总和最小的方案:
8+13+22=43
输入数据:
第二行为每堆的石子数,每两个数之间用一个空格符分隔。
输出数据:
输出文件名为output.txt
从第1至第N行为得分最小的合并方案。
第N+1行是空行。
从第N+2行到第2N+1行是得
分最大合并方案。
每种合并方案用N行表示,其中第i行(1
顺时针次序输出,哪一堆先输出均可)。
要求将待合并的两堆石子数以相应的负数表示,以便标识。
输入输出范例:
输入文件内容:
4594
输出文件内容:
-459-4
-8-59
—13-9
4-5-94
4-14-4-4-18
.算法分析
竞赛中多数选手都不约而同地采用了尽可能逼近目标的贪心法来逐次合并:
从最上面
的一堆开始,沿顺时针方向排成一个序列。
第一次选得分最小(最大)的相邻两堆合并,
,依次类推,
形成新的一堆;接下来,在N-1堆中选得分最小(最大)的相邻两堆合并直至所有石子经N-1次合并后形成一堆。
要求选择一种合并石子的方案,使得做5次合并,得分的总和最小。
按照贪心法,合并的过程如下:
每次合并得分
第一次合并3
->
第二次合并5
第三次合并9
->
第四次合并9
第五次合并1
->24
24
总得分=5+9+9+1
5+24=62
但是当我们仔细琢磨后,可得出另一个合并石子的方案:
每次合并得分
第一次合并3
->7
第二次合并7
->13
第三次合并1
->6
第四次合并1
->11
第五次合并1
->24
总得分=7+6+11+13+24=61
.最佳合并过程符合最佳原理
使用贪心法至所以可能出错,
例如上例中第五次合并石子数分别为13和11的相邻两堆。
为如何使得这两个子序列的N-2
次合并的得分总和最优。
为了实现这一目标,我们将第1个序列又一分为二:
第1、2堆构成子序列1,
第3堆为子序列2。
第一次合并子序列1中的两堆,得分7;
合并方案也是最优的。
过这样的5次合并后,得分的总和最小。
我们把每一次合并划分为阶段,当前阶段中计算出的得分和作为状态,
很显然,某阶段的状态给定后,则以后各阶段的决策不受这阶
如何在前一次合并的基础上定义一个能使目前得分总和最大的合并方案作为一次决策。
段状态的影响。
这种无后效性的性质符最佳原理,因此可以用动态规划的算法求解。
2.动态规划的方向和初值的设定
这些石子堆子序列包括:
采用动态规划求解的关键是确定所有石子堆子序列的最佳合并方案。
它的最佳合并方案包括两个信息:
①在该子序列的各堆石子合并成一堆的过程中,各次合并得分的总和;
因此只需记住子序列1的堆数;
②形成最佳得分和的子序列1和子序列2。
由于两个子序列是相邻的,
c〔i,j〕将〔i,j〕一分为二,其中子序列1的堆数;
显然,对每一堆石子来说,它的
f〔i,1〕=0
c〔i,1〕=0(1wiwN)
对于子序列〔i,j〕来说,若求最小得分总和,f〔i,j〕的初始值为若求最大得分总和,f〔i,j〕的初始值为0。
(1
、第N堆数起,顺时针数2堆)的合
动态规划的方向是顺推(即从上而下)。
先考虑含二堆石子的N个子序列(各子序列分别从第1堆、第2堆、
f〔1,2〕,f〔2,2〕,
,f〔N,2〕
、第N堆数起,顺时针数3堆)的合并方案
然后考虑含三堆石子的N个子序列(各子序列分别从第1堆、第2堆、
f〔N,3〕
、第N堆数起,顺时针数N堆)的合并方案
依次类推,直至考虑了含N堆石子的N个子序列(各子序列分别从第1堆、第2堆、
过程。
f〔1,N〕,f〔2,
c〔1,N〕,c〔2,
最后,在子序列〔1,
N〕,
N〕,
N〕,〔2
,f〔N,N〕
,c〔N,N〕
,N〕,
,〔N,N〕
中,选择得分总和(f值)最小(或最大)的一个子序列〔i,N〕(1Wi
3.动态规划方程和倒推合并过程
当求最大得分总和时
f〔i,j〕=max{f〔i,k〕+f〔x,j-k〕+t}
c〔i,j〕=k
f〔i,j〕=f〔i,k〕+f〔x,j-k〕+t
(2当求最小得分总和时
f〔i,j〕=min{f〔i,k〕+f〔x,j—k〕+t}
c〔i,j〕=k
f〔i,j〕=f〔i,k〕+f〔x,j-k〕+t
其中
x=(i+k—1)
modn+1,即第
i堆数起,顺时针数k+1堆的堆序号。
42)堆石子,按动态规划方程顺推最小得分和。
依次得出含二堆石子的6个子序列的合并方案
f
〔1,2〕
=7
f
〔2,
2〕=1
0f〔3
,2〕=11
c
〔1,2〕
=1
c
〔2,
2〕=1
c〔3,
2〕=1
f
〔4,2〕
=9
f
〔5,
2〕=6
f〔6,
2〕=5
c
〔4,2〕
=1
c
〔5,
2〕=
1c〔6
,2〕=1
例如对上面例子中的6
(346
5
个子序列的合并方案
f
〔1,
3〕
=2
0
f〔2
,3〕
=
25
c
〔1,
3〕
=2
c
〔2,
3〕=
2
c
f
〔4,
3〕
=1
7
f〔5
,3〕
=
14
c
〔4,
3〕
=1
c
〔5,
3〕=
1
c
含三堆石子的6
3
〔3,3〕=1
f〔6,3〕=14
〔6,3〕=2
2)
46
4
3,3〕=24
含四堆石子的6
(3
个子序列的合并方案
f〔1,4〕=3
f〔2,4〕=38
f〔3,4〕=34
c〔1,4〕=2
2,4〕=2c
3,4〕=1
f〔5,4〕=26f〔6,4〕=29
〔5,4〕=2c〔6,4〕=3
f
〔1,
5〕
=5
1f[2,5
=4
8
f〔3,5〕
=4
c
〔1,
5〕
=3
c〔2,5]
——
2
c
〔3,5〕=2
f
〔4,
5〕
=4
1f[5,5
=4
3
f〔6,5〕
=4
c
〔4,
5〕
=2
c〔5,5]
3
c
〔6,5〕=3
含五堆石子的6
46542)个子序列的合并方案
3
f〔2,6〕=62f〔3,6〕=61
〔2,6〕=2c〔3,6〕=2
f〔4,
f〔5,6〕=61f〔6,6〕=62
〔5,6〕=4c〔6,6〕=3
〔l,6〕,f〔2,6〕,
按下述方法倒推合并过程:
第一次合并346
->7
c〔1,3〕=2可知由子序
的最小值,表明最小得分和是由序列〔1,6〕经5次合并得出的。
我们从这个序
、3]和子序列〔4,3〕经4次合并后得出。
其中
I2〕=1,以表明了子序列〔1,2〕的合并方案是第1堆合并第2堆。
->13
第二次合并76
子序列〔1,3〕经2次合并后合并成1堆,
2次合并的得分和=7+13=20。
c〔4,3〕=1,可知由子序列〔4,3〕合并成的一堆石子是由第4堆和子序列〔5,
2〕合并而来的。
而
c〔5,2〕=1,又表明了子序列〔5,2〕的合并方案是第5堆合并第6堆。
由此倒推回去,得出第3、第4次合并的方案
每次合并得分:
第三次合并
42
->6
->11
第四次合并
子序列〔4,3〕经2次合并后合并成1堆,2次合并的得分和=6+11=17。
第五次合并是将最后两堆合并成1堆,该次合并的得分为24。
显然,上述5次合并的得分总和为最小
20+17+24=61
procedureprintbegin
ifj〈〉1then{继续倒推合并过程
beginprint(〔i,c〔i,j〕〕;{倒推子序列1的合并过程}print(〔i+c〔i,j〕-1〕modn+1,j-c〔i,j〕)
forK:
=1toNdo{输出当前被合并的两堆石子}
if(第K堆石子未从圈内去除)
thenbeginif(K=i)or(K=X)then置第K堆石子待合并标志else第K堆石子未被合并;
end;{then}
第i堆石子数J第i堆石子数+第X堆石子数;
将第X堆石子从圈内去除;
{then}{print}
调用print(Cl,6〕)后的结果如下:
print
print(〔1,2〕)①print(〔3,1])
print
(图6.2-5)
(〔1,6〕)⑤
print(〔4,3〕)④
print(〔4,1〕)print(〔5,2〕)3
print
①显示3
②显示7
③显示1
④显示1
⑤显示1
注:
调用print
过程后,应显示6堆石子的总数作为第5次合并的得分。
ProgramStones;
Type
Node=Record{当前序列的合并方案}c:
Longint;{得分和}
d:
Byte{子序列1的堆数}
End;
SumType=Array[1..100,1..100]ofLongint;
{sumtype[i,j]-子序列[ij]的石子总数}
Var
List:
Array[1..100,1..100]ofNode;
子序列[ij]的合并方案}
Date,Dt:
Array[1..100]ofInteger;
Sum:
ASumType;{sum件i,j]-指向子序列[i,j]的石子总数的指针}
F:
Text;{文件变量}
Fn:
String;{文件名串}
Var
Begin
Fork:
=1toNDo{输出当前合并第i堆,第x堆石子的方案}
IfDate[k]>0ThenBegin
If(i=k)or(x=k)ThenWrite(F,-Date[k],'')
ElseWrite(F,Date[k],'')
End;{Then}
Writeln(F);{输出换行符}
Date[i]:
=Date[i]+Date[x];{
原第i堆和第x堆合并成第i堆}
End{Then}
End;{Print}
ProcedureMain(s:
Shortint);
Var
i,j,k:
Integer;
t,x:
Longint;
Begin
Fori:
=1toNDoBegin{
仅含一堆石子的序列不存在合并}
List[i,1].c:
=0;
List[i,1].d:
=0
End;{For}
Forj:
=2toNDo{
Fori:
=1toNDoBegin{
顺推含2堆,含3堆……含N堆石子的各子序列的合并方案}
当前考虑从第i堆数起,顺时针数j堆的子序列}
Ifs=1ThenList[i,j].c:
=Maxlongint{合并[i,j]子序列的得分和初始化}
t:
=Sum仲,j];{
ElseList[i,j].c:
=0;
最后一次合并的得分为[i,j]子序列的石子总数}
Fork:
=1toj-1DoBegin{
子序列1的石子堆数依次考虑1堆……j-1堆}
x:
=(i+k-1)ModN+1;{
求子序列2首堆序号}
If(s=1)And(List[i,k].c+List[x,j-k].c+tOr(s=2)And(List[i,k].c+List[x,j-k].c+t>List[i,j].c)
{若该合并方案为目前最佳,则记下}
ThenBegin
List[i,j].c:
=List[i,k].c+List[x,j-k].c+t;
List[i,j].d:
=k
End{Then}
End{For}
End;{For}
{在子序列[1,N],[2,N],
[N,N]中选择得分总和最小(或最大)的一个子序列}
k:
=1;x:
=List[1,N].c;
Fori:
=2toNDo
If(s=1)And(List[i,N].c(List[i,N].c>x)ThenBegin
k:
=i;x:
=List[i,N].c
End;{Then}
Print(k,N);{由此出发,倒推合并过程}
Writeln(F,Sum件1,N]);{
输出最后一次将石子合并成一堆的石子总数}
Writeln(F);
Writeln(list[k,N].c)
End;{Main}
Begin
Readln(Fn);
Assign(F,Fn);{该文件名串与文件变量连接}
Reset(F);{文件读准备}
读入每堆石子数}
Readln(F,N);{读入石子堆数}
Fori:
=1toNDoRead(F,Date[i]);{
New(Sum);{求每一个子序列的石子数sum}
Fori:
=1toNDoSum件i,1]:
=Date[i];
Forj:
=2toNDo
Fori:
=1toNDo
Sum件i,j]:
=Date[i]+Sum仲ModN+1,j-1];
Dt:
=Date;{暂存合并前的各堆石子,结构相同的变量可相互赋值}
Close(F);{关闭输入文件}
Assign(F,'OUTPUT.TXT');{文件变量与输出文件名串连接}
Rewrite(F);{
文件写准备}
Main
(1);{求得分和最小的合并方案}
Date:
=Dt;{恢复合并前的各堆石子}
Main
(2);{求得分和最大的合并方案}
Close(F){关闭输出文件}
End.