2017清北學堂(提升組精英班)集訓筆記——動態規劃Part2

啊~到下午啦,咱們進入Part2!——一個簡潔的開頭html

咱們來探討第一類問題——路徑行走問題web

經典例題:方格取數(Luogu 1004)算法

設有 N*N 的方格圖 (N<=9),咱們將其中的某些方格中填入正整數,而其餘的方格中則放入數字 0
某人從圖的左上角的 點出發,能夠向下行走,也能夠向右走,直到到達右下角的 點。在走過的路上,他能夠取走方格中的數(取走後的方格中將變爲數字 0)。
此人從 點到 點共走兩次,試找出 條這樣的路徑,使得取得的數之和爲最大。
與數字金字塔很相似?若是隻走一次呢? 

數組

* 只走一次(仿照數字金字塔)記錄 F[i][j] 爲走到第 i 行第 j 列的最大值。
思考:轉移的順序?轉移的方程? 
網站

* 問題:在這道題目當中咱們不能直接套用走一次的方法;一個方格只能被取走一次(也就是說每一個權值只能被取用一次)。 
- 考慮兩條道路同時進行:狀態 F[i][j][k][l] 來記錄第一條路徑走到(i,j),而第二條路徑走到 (k,l) 的最大值。 
spa

* 轉移方程:考慮逆推(我多是由哪些狀態獲得的)設計

在這裏,咱們要保證這兩個點所走的步數是相同的,那麼這個狀態纔是有意義的,在這裏沒有這樣算,不算也是對的,算了也不影響答案。。
- 每一個點能夠往下走或者往右走;一共走到有 2*2=4 種可能性(時刻注意邊界狀況)
3d

 1 //T11:方格取數(DP/逆推)
 2 for(int i=1;i<=n;++i)
 3 for(int j=1;j<=n;++j)
 4 for(int k=1;k<=n;++k)
 5 for(int l=1;l<=n;++l)
 6 {
 7     //注意,若是走到了一塊兒,只加一次
 8     int cost=a[i][j]+a[k][l]-a[i][j]*(i==k&&j==l);//若是兩個位置是重疊的,就要減去重複的 
 9     //四種可能性;考慮:爲何不加邊界狀況的判斷?
10     f[i][j][k][l]=max(max(f[i-1][j][k-1][l],f[i-1][j][k][l-1]),max(f[i][j-1][k-1][l],f[i][j-1][k][l-1]))+cost;
11 }

 最長不降低子序列問題:code

經典例題:導彈攔截(Luogu 1020)orm

某國爲了防護敵國的導彈襲擊,發展出一種導彈攔截系統。可是這種導彈攔截系統有一個缺陷:雖然它的第一發炮彈可以到達任意的高度,可是之後每一發炮彈都不能高於前一發的高度。某天,雷達捕捉到敵國的導彈來襲。因爲該系統還在試用階段,因此只有一套系統,所以有可能不能攔截全部的導彈。
輸入導彈依次飛來的高度(雷達給出的高度數據是不大於 30000 的正整數),計算這套系統最多能攔截多少導彈,若是要攔截全部導彈最少要配備多少套這種導彈攔截系統。 
-389 207 155 300 299 170 158 65 

-最多可以攔截: 6;最少要配備: 2

* LIS:一個序列當中一段不降低的子序列。
* 這道題目中第一問要求咱們找到一段最長的單調降低的子序列(不管是上升仍是降低,可使用相似的算法解決)
* 狀態:咱們用 F[i] 表明,i 位置爲結尾的一段, 最長的降低子序列的長度
! 最優性:若是某段 [q1q2q3 ...qn] 是以qn結尾的最長降低子序列;那麼去掉最後一個的序列 [q1q2q3...qn-1]依然是qn-1結尾的最長降低子序列。(一個這是一個全局最優,每一步的最優解構成全局最優,因此拆成部分仍是最優解,知足無後效性原則)

* 逆推:假設咱們須要求X 結尾最長降低子序列 F[X]
* 最優性可得,咱們除去最後一個位置(也就是 X),仍是一段最長降低子序列
* 那咱們能夠枚舉這個子序列的結尾 Y,最優值就是 F[Y]
! 但須要注意的是,必須保證A[X] < A[Y]X Y 要低,才知足降低的要求。
* 咱們從全部枚舉的結果中找到一個最大的便可

例如:咱們看到這坨綠油油的圖,咱們討論F[8]做爲誰的結尾,由圖中咱們能夠獲得:

F[8]=F[4]+1(F[8]能夠做爲F[4]的結尾);  F[8]=F[6]+1(F[8]一樣能夠做爲F[6]的結尾);  F[8]=F[7]+1(F[8]能夠做爲F[7]的結尾)

可是最長的仍是F[8]做爲F[4]結尾,這時候最長,取max

* 注意到題目還須要計算若是要攔截全部導彈最少要配備多少套這種導彈攔截系統。’
* 能夠直接觀察獲得,所求的答案至少爲原題的最長不降低子序列
- 由於它們當中,任意兩個都不可能被同一個導彈打中。
- 事實能夠證實,這就是答案。—— 啊喂?不給個證實嘛→_→?那確定給啊!證實以下!

證實:由於假設一個導彈a被打中了,那麼下次有比它高的導彈b,就沒辦法用打中a的導彈系統來打b,必須增長一個導彈系統,因此爲最長不降低子序列長度。

一樣地,咱們還能夠運用極限思惟來考慮,有n顆導彈,它們的高度都是單調遞增的,那麼這時就必需要n個系統來攔截,因此爲最長不降低子序列長度=n;

 1 //T12:導彈攔截(DP/LIS/逆推)
 2 int ansf=0,ansg=0;//記錄全部的f(g)中的最優值
 3 //f計算降低子序列,g計算不降低子序列
 4 for(int i=1;i<=n;++i)
 5 {////枚舉倒數第二個,尋找最長降低放到f中,最長不降低放到g中
 6     for(int j=1;j<i;++j)
 7     if(a[j]>a[i]) f[i]=max(f[i],f[j]);
 8     else g[i]=max(g[i],g[j]);
 9     ++f[i],++g[i];//加上本身的一個
10     ansf=max(ansf,f[i]);
11     ansg=max(ansg,g[i]);
12 }
13 cout<<ansf<<endl<<ansg<<endl;//輸出答案

 問題變形:

經典例題:合唱隊形(Luogu 1091)

*N位同窗站成一排,音樂老師要請其中的(N-K)位同窗出列,使得剩下的 位同窗排成合唱隊形。
*合唱隊形是指這樣的一種隊形:設 K 位同窗從左到右依次編號爲1,2,…,K,他們的身高分別爲T1T2,...,TK,則他們的身高知足T1<...<Ti>Ti+1>...>TK(1i≤ K)
* 你的任務是,已知全部 N 位同窗的身高,計算最少須要幾位同窗出列,可使得剩下的同窗排成合唱隊形。
186 186 150 200 160 130 197 220
最少須要 位同窗出列 

所謂合唱隊形,就是要一個隊列高度構成一個山峯的形狀,有頂尖,兩邊單調遞減。

咱們能夠採用枚舉每一個人爲頂尖,從那個定點開始,向左、右尋找單調遞減序列的最大和,這樣。。。真的很麻煩。

* 問題轉化:最少的同窗出列 -> 儘可能多的同窗留在隊列
* LIS 的聯繫:若是肯定了中間的「頂尖」 ,兩側就是「單調上升」 和「單調降低」 的。 
           

 * 狀態設計F[i] G[i](預先處理)
- F[i]i 爲端點,左側的最長的上升子序列長度。
- G[i]i 爲端點右側的最長的降低子序列長度。
這個思路就是要咱們找到每一個以第i個點爲末端的最長上升子序列和最長降低子序列,最終咱們枚舉每個點i訪問f[i]、g[i],找出二者相加最大值便可。

一樣,咱們還有另外一種思路,找到了最長上升子序列和最長降低子序列,兩個序列合併,去掉中間重複的元素(出現頂尖),即爲答案。(這裏我就不粘代碼了,詳見我以前寫這個題的博客吧→_→)

 1 //T13:合唱隊形(DP/LIS/逆推)
 2 for(int i=1;i<=n;++i) cin>>a[i],f[i]=g[i]=1;
 3 for(int i=1;i<=n;++i)
 4 for(int j=1;j<i;++j)
 5     if(a[i]>a[j]) f[i]=max(f[i],f[j]+1);
 6 for(int i=n;i;--i)//g的計算從反方向進行枚舉
 7     for(int j=n;j>i;--j)
 8         if(a[i]>a[j]) g[i]=max(g[i],g[j]+1);
 9 int ans=0;
10 /*把最長上升和最長降低的一部分部分拼在一塊兒求總長度最大*/
11 for(int i=1;i<=n;++i)//枚舉每個頂點爲頂尖
12 {
13     ans=max(ans,f[i]+g[i]-1);//"-1"表示減掉頂尖這個重複計算的點 
14 }

最長公共子序列:

經典例題:排列LCS問題(Luogu 1439)

* 給出 1-n 的兩個排列 P1 P2,求它們的最長公共子序列。
* 公共子序列:既是 P1 的子序列,也是 P2 的子序列。
- 3 2 1 4 5
- 1 2 3 4 5
- 最長公共子序列 (LCS)3([1 4 5])

* LCS:兩個序列的最長公共子序列
* 狀態:咱們用 F[i][j] 表明,前一個序列i 位置爲結尾,後一個序列以 位置爲結尾,它們的最長公共子序列
! 最優性:若是某段 [q1q2q3...qn] 是分別以i,j
結尾的最長公共子序列;那麼去掉最後一個的序列 [q1q2q3...qn-1],依然是以 11結尾的最長公共子序列。 

 * 逆推:假設咱們須要求兩個序列分別以 i,j 結尾最長公共子序列F[i][j],接下來咱們能夠分幾種狀況討論:
- A[i] 不在公共子序列中,那麼長度則等於 F[i-1][j]
- B[j] 不在公共子序列中,那麼長度則等於 F[i][j-1]
- A[i] B[j] 都在子序列中,而且二者匹配,那麼長度等於F[i-1][j-1]+1
* 咱們從全部枚舉的結果中找到一個最大的便可。 

* 逆推:假設咱們須要求兩個序列分別以 i,j 結尾最長公共子序列F[i][j],可能的三種狀況: 

1 //T16:排列LCS問題(DP/LCS/50分數據規模限制)
2 for(int i=1;i<=n;++i)
3     for(int j=1;j<=n;++j)
4     {
5         //分三種狀況進行討論
6         f[i][j]=max(f[i-1][j],f[i][j-1]);//若是兩個相同,娶一個最大值 7         if(p[i]==q[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1);
8     }
9 int ans=f[n][n];

僅僅在這個問題中,LCS是能夠轉化爲LIS的:

* 假定某一個序列爲 [1 2 3 ... N],那麼答案則是另外一個序列的 LIS;(由於知足嚴格的單調性質)
- 3 2 1 4 5
- 1 2 3 4 5
* 但若是兩個序列都不是 [1 2 3 ... N] 呢?經過轉化使一個序列變成它(過程爲:咱們把第一個序列的第一個數5變成1,第二個序列的1變成5;把第一個序列的第二個數3變爲2,第二個序列的2變爲3,以此類推...),而答案不變。
- 5 3 4 1 2 -> 1 2 3 4 5
- 3 5 1 2 4 -> 2 1 4 5 3

這種轉換隻能用在所給序列有不重複元素,長度相同,若是不知足,就會出現重複,會出錯。

問題變形:

經典例題:字串距離(Luogu 1279)

* 設有字符串 X,咱們稱在 X 的頭尾及中間插入任意多個空格後構成的新字符串爲 的擴展串,如字符串 爲」 abcbcd」,則字符串abcb□cd」,「□a□bcbcd□」和「abcb□cd□」都是 的擴展串,這裏」表明空格字符。
* 若是 A1 是字符串 A 的擴展串, B1 是字符串 B 的擴展串, A1 B1具備相同的長度,那麼我捫定義字符串 A1 與 B1 的距離爲相應位置上的字符的距離總和,而兩個非空格字符的距離定義爲它們的 ASCII 碼的的字符的距離總和,而兩個非空格字符的距離定義爲它們的 ASCII 碼的差的絕對值,而空格字符與其餘任意字符之間的距離爲已知的定值 K,空格字符與空格字符的距離爲 0。在字符串 A、 的全部擴展串中,一定存在兩個等長的擴展串 A1、 B1,使得 A1 與 B1 之間的距離達到最小,咱們將這一距離定義爲字符串 A、 的距離。
* 請你寫一個程序,求出字符串 AB 的距離。  

- cmc
- snmn
- 2
- 距離爲:10
  

* 狀態設計:仿照最長公共子序列,咱們設計狀態 F[i][j] 爲前一個序列以 結尾,後一個序列以 結尾的最小距離;一樣也有如下三種狀況
- A[i] 與空格匹配:距離爲 F[i-1][j] + K(K 爲到空格的距離)
- B[j] 與空格匹配:距離爲 F[i][j-1] + K
- A[i] B[j] 匹配:距離爲 F[i-1][j-1] + |A[i] - B[j]|;(ASCII 碼的差的絕對值)

 1 //T17:字串距離(DP/LCS)
 2 //初值:在遇到最小值的問題,必定要當心初值的處理
 3 f[0][0]=0;
 4 for(int i=1;i<=n;++i) f[i][0]=i*k;
 5 for(int j=1;j<=m;++j) f[0][j]=j*k;
 6 for(int i=1;i<=n;++i)//n爲第一個串的長度
 7 for(int j=1;j<=m;++j)//m爲第二個串的長度
 8 {
 9     //三種狀況
10     f[i][j]=min(f[i-1][j],f[i][j-1])+k;
11     f[i][j]=min(f[i][j],f[i-1][j-1]+abs(p[i-1]-q[j-1]));
12 }
13 int ans=f[n][m];

 二維平面問題:

 經典例題:家的範圍(Luogu 2733)

  農民約翰在一片邊長是 N (2 <= N <= 250) 英里的正方形牧場上放牧他的奶牛。 (由於一些緣由,他的奶牛隻在正方形的牧場上吃草。 ) 遺憾的是, 他的奶牛已經毀壞一些土地。 ( 一些 1 平方英里的正方形)
  農民約翰須要統計那些能夠放牧奶牛的正方形牧場 (至少是 2x2 的, 在這些較大的正方形中沒有一個點是被破壞的,也就是說,全部的點都是「 1」 )。
  你的工做要在被供應的數據組裏面統計全部不一樣的正方形放牧區域(>=2x2) 的個數。固然,放牧區域多是重疊。
如右圖所示:以二、三、4爲邊長的正方形共有十、四、1個,不妨轉換一下思路:咱們只考慮以(x,y)這個點能構成的最大正方形是到多大?這樣把問題化簡就好了。

*狀態設計:設狀態 F[i][j] 爲以 (i,j) 位置爲右下角,最大的一個正方形區域。

* 考慮位置 F[i-1][j] 與 F[i][j-1];
- 則有 F[i][j] <= F[i-1][j] + 1; F[i][j] <= F[i][j-1] + 1;

上圖中,*號位置(i,j)的大小由上方和左方決定,*號位置的F[i][j]=4而≠5,由於*號的值不能超過4也不能超過5,因此只能取4,要取F[i-1][j] + 一、F[i][j-1] + 1二者中較小的一個。

很明顯, F[i][j-1] 與 F[i-1][j] 中較小值限制了最大區域的邊長。

* 但在某些狀況下,僅考慮 F[i][j-1] 與 F[i-1][j] 顯然不全面,還須要考慮 F[i-1][j-1]。

* 因此咱們能夠獲得最終的表達式: F[i][j] = MIN(F[i-1][j],F[i][j-1], F[i-1][j-1]) + 1 (當 (i,j) 不爲障礙時)

 1 //T18:家的範圍(DP)
 2 //邊界初值
 3 for(int i=0;i<n;++i) f[i][0]=(a[i][0]=='1');//t[i]表示最大邊長爲i的正方形的個數
 4     for(int j=0;j<n;++j) f[0][j]=(a[0][j]=='1');
 5         for(int i=1;i<n;++i)
 6             for(int j=1;j<n;++j) 
 7                 if(a[i][j]=='1')//若是是非障礙
 8                 {//計算
 9                     f[i][j]=min(min(f[i-1][j],f[i][j-1]),f[i-1][j-1])+1;
10                     t[f[i][j]]++;//當前這個規格爲i的正方形的個數++
11                 }
12 for(int i=n;i;i--)/*統計全部方形的數目——(t[i]已經算出來了以(i,j)這個點爲右下角可以成的最大正方形
13                   總數,可是題目中要求總和,因此這個for就是求以(i,j)爲右下角的正方形總數),
14                   例如:以(i,j)爲右下角的4*4正方形能夠提供一個以(i,j)爲右下角的3*3正方形,
15                   一個以(i,j)爲右下角的2*2正方形…以此類推,倒着累加便可*/
16     t[i-1]+=t[i];
17 for(int i=2;i<=n;++i)
18     if(t[i])//輸出結果
19         cout<<i<<" "<<t[i]<<endl;

區間動態規劃:

經典例題:遊戲 A Game(Luogu 2734)

  有以下一個雙人遊戲:N(2 <= N <= 100) 個正整數的序列放在一個遊戲平臺上,遊戲由玩家 1 開始,兩人輪流從序列的任意一端取一個數,取數後該數字被去掉並累加到本玩家的得分中,當數取盡時,遊戲結束。以最終得分多者爲勝。
  編一個執行最優策略的程序,最優策略就是使玩家在與最好的對手對弈時,能獲得的在當前狀況下最大的可能的總分的策略。你的程序要始終爲第二位玩家執行最優策略。

* 狀態設計方法爲: F[i][j] 表示倘若只用第 i 個數與第 j 個數之間的數進行遊戲, 先手能得到的最高的分爲多少。
* 咱們能夠根據先手取左邊仍是取右邊來決定哪一種狀況得分高。
! 當先手取完一件後, 前後手發生交換,而問題是相似的,以下圖:

在左圖中,咱們已經計算出了七、二、九、五、2中先手能得到的最大值爲14,後手爲11,當先手取第一個數4時,先手=後手+4,後手=先手(發生了交換)。

一樣地,在右圖中,咱們已經計算出了四、七、二、九、5中先手能得到的最大值爲11,後手爲16,當先手取最後一個數2時,先手=後手+2,後手=先手(一樣發生了當前max值的交換)。

- 考慮: 如何決定狀態轉移順序?先算哪些,後算哪些?如何書寫狀態轉移方程?
在例子中,咱們要計算長度爲6的區間內的最優值,咱們必需要算出左邊長度爲5的、右邊長度爲5的兩個區間的最優值,因此能夠先算長度爲1的區間,再算長度爲2的區間,再算長度爲3的區間…就能夠獲得最大區間最優值。

 1 //T19:遊戲
 2 cin>>n;
 3 for(int i=1;i<=n;++i)
 4     cin>>a[i],
 5 s[i]=s[i-1]+a[i];//s表明前綴和
 6 for(int i=1;i<=n;++i) f[i][i]=a[i];//初值(先手最後只剩下一個數,就是我本身)
 7 for(int k=2;k<=n;++k)//按照序列的長度進行枚舉(k爲區間長度) 
 8     for(int i=1,j;i+k-1<=n;++i)
 9     {
10         j=i+k-1;
11         f[i][j]=max(s[j]-s[i]-f[i+1][j]+a[i],s[j-1]-s[i-1]-f[i][j-1]+a[j]);//二者取較大值=max(先取最左邊先手最大得分,先取最右邊先手最大得分) 
12     }             //左邊總和-先手玩家得分+本身當前取得的分數=總得分
13 cout<<f[1][n]<<" "<<s[n]-f[1][n]<<endl;//輸出答案

經典例題:加分二叉樹(Luogu 1040)

  設一個 n 個節點的二叉樹 tree 的中序遍歷爲( 1,2,3,…,n),其中數字 1,2,3,…,n 爲節點編號。每一個節點都有一個分數(均爲正整數),記第 i 個節點的分數爲 di, tree 及它的每一個子樹都有一個加分,任一棵子樹 subtree(也包含 tree 自己)的加分計算方法以下:
  subtree 的左子樹的加分 × subtree 的右子樹的加分+ subtree 的根的分數。
  若某個子樹爲空,規定其加分爲 1,葉子的加分就是葉節點自己的分數,不考慮它的空子樹。
  試求一棵符合中序遍歷爲( 1,2,3,…,n)且加分最高的二叉樹 tree。要求輸出;
1.tree 的最高加分:
2.tree 的前序遍歷:
- 5 7 1 2 10
- 答案 1: 145
- 答案 2: 3 1 2 4 5
設 F[i][j] 爲只用第 i 個數到第 j 個數構成的加分樹的最大權值。下圖爲樣例解釋:

牢記一個二叉樹的性質:中序遍歷時候,左右子樹必定在根節點左右兩邊

* 枚舉根節點,這樣就化成了左子樹和右子樹的問題,求最優解便可。
* F[i][j] = MAX ( F[i][k-1] * F[k+1][j] + A[k] )(左×右+根k本身自己權值)

 1 //T25:加分二叉樹
 2 for(int i=1;i<=n;++i) f[i][i]=a[i];//賦初值(只有一個葉子節點,根就是本身) 
 3 for(int i=0;i<=n;++i) f[i+1][i]=1;
 4 for(int k=1;k<n;++k)
 5     for(int i=1;i+k<=n;++i)
 6     {
 7         int j=i+k;
 8         for(int l=i;l<=j;++l) f[i][j]=max(f[i][j],f[i][l-1]*f[l+1][j]+a[l]);//枚舉根節點 
 9     }
10 int ans=f[1][n];

* 問題: 如何求出樹的前序遍歷(樹的形態)

咱們另外記錄一個輔助數組 G[i][j],表明 F[i][j] 取最大值的時候,根節點是什麼,這樣就能夠經過遞歸來求出樹的前序遍歷。

 1 for(int i=1;i<=n;++i) f[i][i]=a[i],g[i][i]=i;//邊界值(只有一個葉子節點,根就是本身) 
 2 for(int i=0;i<=n;++i) f[i+1][i]=1;//預處理空節點,保證不出錯,一個根節點沒有左子樹,把左子樹標記爲1 
 3 for(int k=1;k<n;++k)//k:區間長度 
 4 for(int i=1;i+k<=n;++i)
 5 {
 6     int j=i+k;//j:末尾節點 
 7     for(int l=i;l<=j;++l)
 8     {
 9         long long t=f[i][l-1]*f[l+1][j]+a[l];
10         if(t>f[i][j])//記錄最優的根
11         {
12             f[i][j]=t;
13             g[i][j]=l;
14         } 
15     }
16 }
 1 //T25:加分二叉樹
 2 //遞歸輸出x到y這個樹的前綴遍歷
 3 void dfs(int x,int y)
 4 {
 5     if(x>y) return;
 6     int l=g[x][y];//l爲根
 7     cout<<l<<" ";//先輸出l
 8     /*=====================*///再輸出子樹的值
 9     dfs(x,l-1);//
10     dfs(l+1,y);//
11     /*=====================*/
12 }
13 ...
14 //輸出答案——整棵樹
15 dfs(1,n);

過程型狀態劃分:

經典例題:傳球遊戲(Luogu 1057)

  上體育課的時候,小蠻的老師常常帶着同窗們一塊兒作遊戲。此次,老師帶着同窗們一塊兒作傳球遊戲。
  遊戲規則是這樣的: n 個同窗站成一個圓圈,其中的一個同窗手裏拿着一個球,當老師吹哨子時開始傳球,每一個同窗能夠把球傳給本身左右的兩個同窗中的一個(左右任意),當老師在此吹哨子時,傳球中止,此時,拿着球沒有傳出去的那個同窗就是敗者,要給你們表演一個節目。
  聰明的小蠻提出一個有趣的問題:有多少種不一樣的傳球方法可使得從小蠻手裏開始傳的球,傳了 m 次之後,又回到小蠻手裏。兩種傳球方法被視做不一樣的方法,當且僅當這兩種方法中,接到球的同窗按接球順序組成的序列是不一樣的。好比有三個同窗 1 號、 2 號、 3 號,並假設小蠻爲 1號,球傳了 3 次回到小蠻手裏的方式有 1->2->3->1 和 1->3->2->1,共 2 種。

* 分析:這道題目十分容易用搜索解決。 爲何?
* 由於題目已經明確給定了過程:傳球的次數。
* 由於這個過程是必定按照順序進行的,因此能夠直接寫出狀態:
* 設狀態 F[i][j] 爲傳到第 i 次,如今在第 j 我的手上的方案數。
* 很顯然 F[i] 只和 F[i-1] 有關;由於題目已經規定好了傳遞順序。
! 這一類的過程型問題只須要找出事情發展的順序,就能夠很簡單的寫出狀態與轉移方程。

 1 //T26:傳球遊戲
 2 f[0][1]=1;//賦初值
 3 for(int i=1;i<=m;++i)
 4 {
 5     f[i][1]=f[i-1][2]+f[i-1][n];//頭與尾須要特殊處理(第1號從2號和n號傳過來)
 6     f[i][n]=f[i-1][n-1]+f[i-1][1];
 7     for(int j=2;j<n;++j)//轉移只和i-1有關係(由於我已經規定好了發展順序)
 8         f[i][j]=f[i-1][j-1]+f[i-1][j+1];//能夠在j-一、j+1手裏(加法原理)
 9 }
10 int ans=f[m][1];

經典例題:烏龜棋(Luogu 1541)

  烏龜棋的棋盤是一行 N 個格子,每一個格子上一個分數(非負整數)。棋盤第 1 格是惟一的起點,第 N 格是終點,遊戲要求玩家控制一個烏龜棋子從起點出發走到終點。
  烏龜棋中 M 張爬行卡片,分紅 4 種不一樣的類型( M 張卡片中不必定包含全部 4 種類型的卡片,見樣例),每種類型的卡片上分別標有 一、 二、 三、4 四個數字之一,表示使用這種卡片後,烏龜棋子將向前爬行相應的格子數。遊戲中,玩家每次須要從全部的爬行卡片中選擇一張以前沒有使用過的爬行卡片,控制烏龜棋子前進相應的格子數,每張卡片只能使用一次。
  遊戲中,烏龜棋子自動得到起點格子的分數,而且在後續的爬行中每到達一個格子,就獲得該格子相應的分數。玩家最終遊戲得分就是烏龜棋子從起點到終點過程當中到過的全部格子的分數總和。

- 棋盤: 6 10 14 2 8 8 18 5 17
- 卡片: 1 3 1 2 1
- 答案: 73 = 6+10+14+8+18+17

很明顯,用不一樣的爬行卡片使用順序會使得最終遊戲的得分不一樣,小明想要找到一種卡片使用順序使得最終遊戲得分最多。如今,告訴你棋盤上每一個格子的分數和全部的爬行卡片,你能告訴小明,他最多能獲得多少分嗎?

* 思考:如何進行搜索?狀態該如何設計?
* DFS(x,c1,c2,c3,c4) 爲當前在第 x 個格子上, ci 表明標有數字i 的卡片有多少張。
* 因而能夠直接寫出狀態 F[i][a][b][c][d],與狀態是一一對應的,表示該狀態下, 最大的權值是多少(當前第i個格子,四種牌分別用了i,j,k,l張的狀況下,能達到的最優值)。
* 因而,能夠和 F[i-1],F[i-2]…進行聯繫

 1 //T27:烏龜棋(我把i省略了,其實沒什麼用,加上只是用來判斷是否越界而已)
 2 for(int i=0;i<=a;i++)
 3     for(int j=0;j<=b;j++)
 4         for(int k=0;k<=c;k++)
 5             for(int l=0;l<=d;l++)
 6             {
 7                 if(i!=0)f[i][j][k][l]=max(f[i][j][k][l],f[i-1][j][k][l]+s[i+j*2+k*3+l*4]);
 8                 if(j!=0)f[i][j][k][l]=max(f[i][j][k][l],f[i][j-1][k][l]+s[i+j*2+k*3+l*4]);
 9                 if(k!=0)f[i][j][k][l]=max(f[i][j][k][l],f[i][j][k-1][l]+s[i+j*2+k*3+l*4]);
10                 if(l!=0)f[i][j][k][l]=max(f[i][j][k][l],f[i][j][k][l-1]+s[i+j*2+k*3+l*4]);
11             }

最近發現一些網站盜用個人blog,這實在不能忍(™把關於個人名字什麼的所有刪去只保留文本啥意思。。)!!但願各位轉載引用時請註明出處,謝謝配合噢~

原博客惟一地址:http://www.cnblogs.com/geek-007/p/7197439.html

相關文章
相關標籤/搜索