主題教育網(wǎng)站建立seo廣告投放
一.試題
??????在一個(gè)園形操場(chǎng)的四周擺放N堆石子(N≤100),現(xiàn)要將石子有次序地合并成一堆。規(guī)定
??????每次只能選相鄰的兩堆合并成新的一堆,并將新的一堆的石子數(shù),記為該次合并的得分。
??????編一程序,由文件讀入堆數(shù)N及每堆的石子數(shù)(≤20),
??????①選擇一種合并石子的方案,使得做N-1次合并,得分的總和最小;
??????②選擇一種合并石子的方案,使得做N-1次合并,得分的總和最大。
??????例如,所示的4堆石子,每堆石子數(shù)(從最上面的一堆數(shù)起,順時(shí)針數(shù))依
??????次為4594。則3次合并得分總和最小的方案:8+13+22=43
??????得分最大的方案為:14+18+22=54
??????輸入數(shù)據(jù):
??????文件名由鍵盤輸入,該文件內(nèi)容為:
??????第一行為石子堆數(shù)N;
??????第二行為每堆的石子數(shù),每?jī)蓚€(gè)數(shù)之間用一個(gè)空格符分隔。
??????輸出數(shù)據(jù):
??????輸出文件名為output.txt
??????從第1至第N行為得分最小的合并方案。第N+1行是空行。從第N+2行到第2N+1行是得
??????分最大合并方案。
??????每種合并方案用N行表示,其中第i行(1≤i≤N)表示第i 次合并前各堆的石子數(shù)(依
??????順時(shí)針次序輸出,哪一堆先輸出均可)。 要求將待合并的兩堆石子數(shù)以相應(yīng)的負(fù)數(shù)表示,以便標(biāo)識(shí)。
??????輸入輸出范例:
??????輸入文件內(nèi)容:
??????4
??????4 59 4
??????輸出文件內(nèi)容:
??????-4 5 9 -4
??????-8-5 9
??????-13 -9
??????22
???????
??????4 -5 -9 4
??????4 -14 -4
??????-4-18
??????22
??????二.算法分析
??????競(jìng)賽中多數(shù)選手都不約而同地采用了盡可能逼近目標(biāo)的貪心法來(lái)逐次合并:從最上面
??????的一堆開(kāi)始,沿順時(shí)針?lè)较蚺懦梢粋€(gè)序列。 第一次選得分最小(最大)的相鄰兩堆合并,
??????形成新的一堆;接下來(lái),在N-1堆中選得分最小(最大)的相鄰兩堆合并……,依次類推,
??????直至所有石子經(jīng)N-1次合并后形成一堆。
??????例如有6堆石子,每堆石子數(shù)(從最上面一堆數(shù)起,順時(shí)針數(shù))依次為3 46 5 4 2
??????要求選擇一種合并石子的方案,使得做5次合并,得分的總和最小。
??????按照貪心法,合并的過(guò)程如下:
??????每次合并得分
??????第一次合并 3 4 6 5 4 2 ->5
??????第二次合并 5 4 6 5 4 ???->9
??????第三次合并 9 6 5 4 ??????->9
??????第四次合并 9 6 9 ?????????->15
??????第五次合并 15 9 ??????????->24
??????24
??????總得分=5+9+9+15+24=62
??????但是當(dāng)我們仔細(xì)琢磨后,可得出另一個(gè)合并石子的方案:
??????每次合并得分
??????第一次合并 3 4 6 5 4 2 ?->7
??????第二次合并 7 6 5 4 2 ????->13
??????第三次合并 13 5 4 2 ?????->6
??????第四次合并 13 5 6 ????????->11
??????第五次合并 13 11 ?????????->24
??????24
??????總得分=7+6+11+13+24=61
??????顯然,后者比貪心法得出的合并方案更優(yōu)。 題目中的示例故意造成一個(gè)貪心法解題的
??????假像,誘使讀者進(jìn)入“陷阱”。為了幫助讀者從這個(gè)“陷阱”里走出來(lái), 我們先來(lái)明確一個(gè)問(wèn)題:
??????1.最佳合并過(guò)程符合最佳原理
??????使用貪心法至所以可能出錯(cuò),
??????是因?yàn)槊恳淮芜x擇得分最小(最大)的相鄰兩堆合并,不一定保證余下的合并過(guò)程能導(dǎo)致最優(yōu)解。聰明的讀者馬上會(huì)想到一種理想的假設(shè):如果N-1次合并的全局最優(yōu)解包含了每一次合并的子問(wèn)題的最優(yōu)解,那么經(jīng)這樣的N-1次合并后的得分總和必然是最優(yōu)的。
??????例如上例中第五次合并石子數(shù)分別為13和11的相鄰兩堆。
??????這兩堆石頭分別由最初的第1,2,3堆(石頭數(shù)分別為3,4,6)和第4,5,6堆(石頭數(shù)分別為5,4,2)經(jīng)4次合并后形成的。于是問(wèn)題又歸結(jié)為如何使得這兩個(gè)子序列的N-2
??????次合并的得分總和最優(yōu)。為了實(shí)現(xiàn)這一目標(biāo),我們將第1個(gè)序列又一分為二:第1、2堆構(gòu)成子序列1,
??????第3堆為子序列2。第一次合并子序列1中的兩堆,得分7;
??????第二次再將之與子序列2的一堆合并,得分13。顯然對(duì)于第1個(gè)子序列來(lái)說(shuō),這樣的合并方案是最優(yōu)的。同樣,我們將第2個(gè)子序列也一分為二;第4堆為子序列1,第5,6堆構(gòu)成子序列2。第三次合并子序列2中的2堆,得分6;第四次再將之與子序列1中的一堆合并,得分13。顯然對(duì)于第二個(gè)子序列來(lái)說(shuō),這樣的合并方案也是最優(yōu)的。
??????由此得出一個(gè)結(jié)論──6堆石子經(jīng)
??????過(guò)這樣的5次合并后,得分的總和最小。我們把每一次合并劃分為階段,當(dāng)前階段中計(jì)算出的得分和作為狀態(tài),
??????如何在前一次合并的基礎(chǔ)上定義一個(gè)能使目前得分總和最大的合并方案作為一次決策。很顯然,某階段的狀態(tài)給定后,則以后各階段的決策不受這階段以前各段狀態(tài)的影響。
??????這種無(wú)后效性的性質(zhì)符最佳原理,因此可以用動(dòng)態(tài)規(guī)劃的算法求解。
??????2.動(dòng)態(tài)規(guī)劃的方向和初值的設(shè)定
??????采用動(dòng)態(tài)規(guī)劃求解的關(guān)鍵是確定所有石子堆子序列的最佳合并方案。 這些石子堆子序列包括:
??????{第1堆、第2堆}、{第2堆、第3堆}、……、{第N堆、第1堆};
??????{第1堆、第2堆、第3堆}、{第2堆、第3堆、第4堆}、……、{第N堆、第1堆、第2堆};……
??????{第1堆、……、第N堆}{第1堆、……、第N堆、第1堆}……{第N堆、第1堆、……、第N-1堆}
??????為了便于運(yùn)算,我們用〔i,j〕表示一個(gè)從第i堆數(shù)起,順時(shí)針數(shù)j堆時(shí)的子序列{第i堆、第i+1堆、……、第(i+j-1)mod n堆}
??????它的最佳合并方案包括兩個(gè)信息:
??????①在該子序列的各堆石子合并成一堆的過(guò)程中,各次合并得分的總和;
??????②形成最佳得分和的子序列1和子序列2。由于兩個(gè)子序列是相鄰的, 因此只需記住子序列1的堆數(shù);
??????設(shè)
??????f〔i,j〕──將子序列〔i,j〕中的j堆石子合并成一堆的最佳得分和;
??????c〔i,j〕──將〔i,j〕一分為二,其中子序列1的堆數(shù);
??????(1≤i≤N,1≤j≤N)
??????顯然,對(duì)每一堆石子來(lái)說(shuō),它的
??????f〔i,1〕=0
??????c〔i,1〕=0 (1≤i≤N)
??????對(duì)于子序列〔i,j〕來(lái)說(shuō),若求最小得分總和,f〔i,j〕的初始值為∞; 若求最大得分總和,f〔i,j〕的初始值為0。(1≤i≤N,2≤j≤N)。
??????動(dòng)態(tài)規(guī)劃的方向是順推(即從上而下)。先考慮含二堆石子的N個(gè)子序列(各子序列分別從第1堆、第2堆、……、第N堆數(shù)起,順時(shí)針數(shù)2堆)的合并方案
??????f〔1,2〕,f〔2,2〕,……,f〔N,2〕
??????c〔1,2〕,c〔2,2〕,……,c〔N,2〕
??????然后考慮含三堆石子的N個(gè)子序列(各子序列分別從第1堆、第2堆、……、第N堆數(shù)起,順時(shí)針數(shù)3堆)的合并方案
??????f〔1,3〕,f〔2,3〕,……,f〔N,3〕
??????c〔1,3〕,c〔2,3〕,……,c〔N,3〕
??????……
??????依次類推,直至考慮了含N堆石子的N個(gè)子序列(各子序列分別從第1堆、第2堆、 ……、第N堆數(shù)起,順時(shí)針數(shù)N堆)的合并方案
??????f〔1,N〕,f〔2,N〕,……,f〔N,N〕
??????c〔1,N〕,c〔2,N〕,……,c〔N,N〕
??????最后,在子序列〔1,N〕,〔2,N〕,……,〔N,N〕中,選擇得分總和(f值)最小(或最大)的一個(gè)子序列〔i,N〕(1≤i≤N),由此出發(fā)倒推合并過(guò)程。
??????3.動(dòng)態(tài)規(guī)劃方程和倒推合并過(guò)程
??????對(duì)子序列〔i,j〕最后一次合并,其得分為第i堆數(shù)起,順時(shí)針數(shù)j堆的石子總數(shù)t。被合并的兩堆石子是由子序列〔i,k〕和〔(i+k-1)mod
??????n+1,j-k〕(1≤k≤j-1)經(jīng)有限次合并形成的。為了求出最佳合并方案中的k值,我們定義一個(gè)動(dòng)態(tài)規(guī)劃方程:
??????當(dāng)求最大得分總和時(shí)
??????f〔i,j〕=max{f〔i,k〕+f〔x,j-k〕+t}
??????1≤k≤j-1
??????c〔i,j〕=k│ f〔i,j〕=f〔i,k〕+f〔x,j-k〕+t
??????(2≤j≤n,1≤i≤n)
??????當(dāng)求最小得分總和時(shí)
??????f〔i,j〕=min{f〔i,k〕+f〔x,j-k〕+t}
??????1≤k≤j-1
??????c〔i,j〕=k│ f〔i,j〕=f〔i,k〕+f〔x,j-k〕+t
??????(2≤j≤n,1≤i≤n)
??????其中x=(i+k-1)modn+1,即第i堆數(shù)起,順時(shí)針數(shù)k+1堆的堆序號(hào)。
??????例如對(duì)上面例子中的6(3 4 6 5 4 2 )堆石子,按動(dòng)態(tài)規(guī)劃方程順推最小得分和。 依次得出含二堆石子的6個(gè)子序列的合并方案
??????f〔1,2〕=7 f〔2,2〕=10 f〔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〕=1 c〔6,2〕=1
??????含三堆石子的6(3 4 6 5 4 2 )個(gè)子序列的合并方案
??????f〔1,3〕=20 f〔2,3〕=25 f〔3,3〕=24
??????c〔1,3〕=2 c〔2,3〕=2 c〔3,3〕=1
??????f〔4,3〕=17 f〔5,3〕=14 f〔6,3〕=14
??????c〔4,3〕=1 c〔5,3〕=1 c〔6,3〕=2
??????含四堆石子的6(3 4 6 5 4 2 )個(gè)子序列的合并方案
??????f〔1,4〕=36 f〔2,4〕=38 f〔3,4〕=34
??????c〔1,4〕=2 c〔2,4〕=2 c〔3,4〕=1
??????f〔4,4〕=28 f〔5,4〕=26 f〔6,4〕=29
??????c〔4,4〕=1 c〔5,4〕=2 c〔6,4〕=3
??????含五堆石子的6(3 4 6 5 4 2 )個(gè)子序列的合并方案
??????f〔1,5〕=51 f〔2,5〕=48 f〔3,5〕=45
??????c〔1,5〕=3 c〔2,5〕=2 c〔3,5〕=2
??????f〔4,5〕=41 f〔5,5〕=43 f〔6,5〕=45
??????c〔4,5〕=2 c〔5,5〕=3 c〔6,5〕=3
??????含六堆石子的6(3 4 6 5 4 2 )個(gè)子序列的合并方案
??????f〔1,6〕=61 f〔2,6〕=62 f〔3,6〕=61
??????c〔1,6〕=3 c〔2,6〕=2 c〔3,6〕=2
??????f〔4,6〕=61 f〔5,6〕=61 f〔6,6〕=62
??????c〔4,6〕=3 c〔5,6〕=4 c〔6,6〕=3
??????f〔1,6〕是f〔1,6〕,f〔2,6〕,……f〔6,6〕中的最小值,表明最小得分和是由序列〔1,6〕經(jīng)5次合并得出的。我們從這個(gè)序列出發(fā),
??????按下述方法倒推合并過(guò)程:
??????由c〔1,6〕=3可知,第5次合并的兩堆石子分別由子序列〔1,3〕和子序列〔4,3〕經(jīng)4次合并后得出。其中c〔1,3〕=2可知由子序列〔1,3〕合并成的一堆石子是由子序列〔1,2〕和第三堆合并而來(lái)的。而c〔1,2〕=1,以表明了子序列〔1,2〕的合并方案是第1堆合并第2堆。
??????由此倒推回去,得出第1,第2次合并的方案,每次合并得分
??????第一次合并 3 4 6…… ->7
??????第二次合并 7 6…… ?????????->13
??????13……
??????子序列〔1,3〕經(jīng)2次合并后合并成1堆, 2次合并的得分和=7+13=20。
??????c〔4,3〕=1,可知由子序列〔4,3〕合并成的一堆石子是由第4堆和子序列〔5,
??????2〕合并而來(lái)的。而c〔5,2〕=1,又表明了子序列〔5,2〕的合并方案是第5堆合并第6堆。由此倒推回去,得出第3、第4次合并的方案
??????每次合并得分:
???????第三次合并 ……54 2 ????->6
??????第四次合并 ……5 6 ???????->11
??????……11
??????子序列〔4,3〕經(jīng)2次合并后合并成1堆,2次合并的得分和=6+11=17。
??????第五次合并是將最后兩堆合并成1堆,該次合并的得分為24。
??????顯然,上述5次合并的得分總和為最小
??????20+17+24=61
??????上述倒推過(guò)程,可由一個(gè)print(〔子序列〕)的遞歸算法描述
??????procedure print (〔i,j〕)
??????begin
??????if j〈〉1 then {繼續(xù)倒推合并過(guò)程
??????begin
??????print(〔i,c〔i,j〕〕;{倒推子序列1的合并過(guò)程}
??????print(〔i+c〔i,j〕-1〕mod n+1,j-c〔i,j〕)
??????{倒推子序列2的合并過(guò)程}
??????for K:=1 to N do{輸出當(dāng)前被合并的兩堆石子}
??????if (第K堆石子未從圈內(nèi)去除)
??????then begin
??????if(K=i)or(K=X)then置第K堆石子待合并標(biāo)志
??????else第K堆石子未被合并;
??????end;{then}
??????第i堆石子數(shù)←第i堆石子數(shù)+第X堆石子數(shù);
??????將第X堆石子從圈內(nèi)去除;
??????end;{then}
??????end;{print}
??????例如,調(diào)用print(〔1,6〕)后的結(jié)果如下:
???????????????????????????print(〔1,6〕)⑤
????????????????????????????????┌──────┴──────┐
?????????????????????print(〔1,3〕)② ??????????????print(〔4,3〕)④
????????????????┌─────┴─────┐ ??????????????┌─────┴─────┐
??????????print(〔1,2〕)① print(〔3,1〕) ????print(〔4,1〕) print(〔5,2〕)③
???????┌──────┴──────┐ ????????????????????????┌──────┴──────┐ ?????????????
??????print(〔1,1〕) ???????print(〔2,1〕) ??????????????print(〔5,1〕) ????????
??????print(〔6,1〕)
??????(圖6.2-5)
??????其中回溯至
??????① 顯示 3 46 5 4
??????② 顯示 7 65 4 2
??????③ 顯示 13 54 2
??????④ 顯示 135 6
??????⑤ 顯示 13 11
??????注:調(diào)用print過(guò)程后,應(yīng)顯示6堆石子的總數(shù)作為第5次合并的得分。
??????Program Stones;
??????Type
??????Node = Record{當(dāng)前序列的合并方案}
??????c : Longint;{得分和}
??????d : Byte{子序列1的堆數(shù)}
??????End;
??????SumType = Array [1..100,1..100] of Longint;
??????{sumtype[i,j]-子序列[i,j]的石子總數(shù)}
??????Var
??????List : Array [1..100,1..100] of Node;
??????{list[i,j]-子序列[i,j]的合并方案}
??????Date, Dt : Array [1..100] of Integer;
??????{Date[i]-第i堆石子數(shù),Dt-暫存Date}
??????Sum : ^SumType;{sum^[i,j]-指向子序列[i,j]的石子總數(shù)的指針}
??????F : Text;{文件變量}
??????Fn : String;{文件名串}
??????N, i, j : Integer;{N-石子堆數(shù),i,j-循環(huán)變量}
??????Procedure Print(i, j : Byte);{遞歸打印子序列[i,j]的合并過(guò)程}
??????Var
??????k, x : Shortint;{k-循環(huán)變量;x-子序列2中首堆石子的序號(hào)}
??????Begin
??????If j <> 1 Then Begin{繼續(xù)倒推合并過(guò)程}
????????Print(i, List[i,j].d);{倒推子序列1的合并過(guò)程}
????????x := (i + List[i, j].d - 1) Mod N + 1;{求子序列2中首堆石子的序號(hào)}
????????Print(x, j - List[i, j].d);{倒推子序列2的合并過(guò)程}
????????For k := 1 to N Do{輸出當(dāng)前合并第i堆,第x堆石子的方案}
??????????If Date[k] > 0 Then Begin
?????????????If (i= k)or(x=k)Then Write(F, - Date[k], ' ')
?????????????Else Write(F, Date[k], ' ')
??????????End; { Then }
????????Writeln(F);{輸出換行符}
????????Date[i] := Date[i] + Date[x];{原第i堆和第x堆合并成第i堆}
????????Date[x] := - Date[x]{將原第x堆從圈內(nèi)去除}
????????End { Then }
??????End; { Print }
??????Procedure Main(s : Shortint);
??????Var
????????i, j, k : Integer;
????????t, x : Longint;
??????Begin
????????For i := 1 to N Do Begin{僅含一堆石子的序列不存在合并}
??????????List[i, 1].c := 0;
??????????List[i, 1].d := 0
????????End; {For}
????????For j := 2 to N Do{順推含2堆,含3堆……含N堆石子的各子序列的合并方案}
??????????For i := 1 to N Do Begin{當(dāng)前考慮從第i堆數(shù)起,順時(shí)針數(shù)j堆的子序列}
????????????If s = 1 Then List[i, j].c := Maxlongint{合并[i,j]子序列的得分和初始化}
?????????????Else List[i, j].c := 0;
????????????t := Sum^[i, j];{最后一次合并的得分為[i,j]子序列的石子總數(shù)}
????????????For k := 1 to j - 1 Do Begin{子序列1的石子堆數(shù)依次考慮1堆……j-1堆}
??????????????x := (i + k - 1) Mod N + 1;{求子序列2首堆序號(hào)}
??????????????If (s=1) And (List[i,k].c + List[x,j-k].c+t < List[i, j].c)
??????Or (s=2) And (List[i,k].c + List[x,j-k].c+t > List[i, j].c)
??????{ 若該合并方案為目前最佳,則記下}
??????????????Then Begin
????????????????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]中選擇得分總和最小(或最大)的一個(gè)子序列}
????????k := 1; x := List[1, N].c;
????????For i := 2 to N Do
??????????If (s = 1) And (List[i, N].c < x) Or (s = 2) And
??????(List[i, N].c > x) Then Begin
?????????????k := i; x := List[i, N].c
??????????End; { Then }
????????Print(k, N);{由此出發(fā),倒推合并過(guò)程}
????????Writeln(F, Sum^[1, N]);{輸出最后一次將石子合并成一堆的石子總數(shù)}
????????Writeln(F);
????????Writeln(list[k, N].c)
??????End; { Main }
??????Begin
????????Write('File name = ');{輸入文件名串}
????????Readln(Fn);
????????Assign(F, Fn);{該文件名串與文件變量連接}
????????Reset(F);{文件讀準(zhǔn)備}
????????Readln(F, N);{讀入石子堆數(shù)}
????????For i := 1 to N Do Read(F, Date[i]);{讀入每堆石子數(shù)}
????????New(Sum);{求每一個(gè)子序列的石子數(shù)sum}
????????For i := 1 to N Do Sum^[i, 1] := Date[i];
????????For j := 2 to N Do
??????????For i := 1 to N Do
????????????Sum^[i, j] := Date[i] + Sum^[i Mod N + 1, j - 1];
????????Dt := Date;{暫存合并前的各堆石子,結(jié)構(gòu)相同的變量可相互賦值}
????????Close(F);{關(guān)閉輸入文件}
????????Assign(F, 'OUTPUT.TXT');{文件變量與輸出文件名串連接}
????????Rewrite(F);{文件寫準(zhǔn)備}
????????Main(1);{求得分和最小的合并方案}
????????Date := Dt;{恢復(fù)合并前的各堆石子}
????????Main(2);{求得分和最大的合并方案}
????????Close(F){關(guān)閉輸出文件}