『嗨威說』演算法設計與分析 - 動態規劃思想小結(HDU 4283 You Are the One)
本文索引目錄:
一、動態規劃的基本思想
二、數字三角形、最大子段和(PTA)遞迴方程
三、一道區間動態規劃題點撥昇華動態規劃思想
四、結對程式設計情況
一、動態規劃的基本思想:
1.1 基本概念:
動態規劃演算法簡單說,利用拆解問題思想,定義問題狀態和狀態之間的關係,使得問題能夠以遞推或者是分治的方式去解決。
動態規劃演算法的基本思想與分治法很相似,將待求解的問題分解為若干個子問題,前一子問題的解,為後一子問題的求解所依賴。在求解任一子問題時,列出各種可能的區域性解,通過決策保留那些有可能達到最優的區域性解,丟棄其他區域性解。依次解決各子問題,最後一個子問題就是初始問題的解。
1.2 使用條件:
(1)具有最優子結構。
具有最優子結構也可能是適合用貪心的方法求解。
注意要確保我們考察了最優解中用到的所有子問題。
1、證明問題最優解的第一個組成部分是做出一個選擇;
2、對於一個給定問題,在其可能的第一步選擇中,你界定已經知道哪種選擇才會得到最優解。你現在並不關心這種選擇具體是如何得到的,只是假定已經知道了這種選擇;
3、給定可獲得的最優解的選擇後,確定這次選擇會產生哪些子問題,以及如何最好地刻畫子問題空間;
4、證明作為構成原問題最優解的組成部分,每個子問題的解就是它本身的最優解。方法是反證法,考慮加入某個子問題的解不是其自身的最優解,那麼就可以從原問題的解中用該子問題的最優解替換掉當前的非最優解,從而得到原問題的一個更優的解,從而與原問題最優解的假設矛盾。
要保持子問題空間儘量簡單,只在必要時擴充套件。 最優子結構的不同體現在兩個方面: 原問題的最優解中涉及多少個子問題; 確定最優解使用哪些子問題時,需要考察多少種選擇。 子問題圖中每個定點對應一個子問題,而需要考察的選擇對應關聯至子問題頂點的邊。
(2)子問題重疊。
子問題空間要足夠小,即問題的遞迴演算法會反覆地求解相同的子問題,而不是一直生成新的子問題。
1.3 使用思想:
(1)首先是拆分問題,將問題劃分成一步一步這樣就可以通過遞推或者遞迴來實現。
(2)其次定義問題狀態和狀態之間的關係,前面拆分的步驟之間的關係,用一種量化的形式表現出來,也即狀態轉移方程式。
1.4 動態規劃的問題種類:
(1)揹包動態規劃問題
(2)區間動態規劃問題
(3)有向無環圖的動態規劃問題
(4)樹形動態規劃問題
(5)狀態壓縮動態規劃問題
(6)數位動態規劃問題
(7)插頭動態規劃問題
(8)計數動態規劃問題
(9)實時動態規劃問題
(10)動態規劃優化問題等
二、數字三角形、最大子段和(PTA)遞迴方程:
數字三角形:/* 動規轉移方程: dp[i][j] = temp[i][j] + max(dp[i+1][j],dp[i+1][j+1]);*/
dp[i][j]表示第i行第j個當前已走數字最大總和。
最大子段和:/* 動規轉移方程: dp[i] = max(dp[i-1]+num[i],num[i]);*/
dp[i]表示以num[i]作為結尾元素時的最大欄位和。
三、一道區間動態規劃題點撥昇華動態規劃思想:
2.1 題目來源:
Vjudge:https://vjudge.net/contest/112701#problem/G
2.2 題目題幹:
You Are the One
2.3 題目大意:
題目意思是:給定一個有序隊伍,在進入舞臺前,需要先進入一個狹小的黑屋子,最先進去的人最後出來,進入舞臺的順序會影響到每個人的憤怒值,每個人上臺的時候的憤怒值為第k個人上場乘以自己的屌絲值,試問怎麼安排人員進出狹小黑屋子能實現最小化憤怒值?
這個問題簡化來說,就是給定一個有序佇列,進入一個棧中(也即題目的狹小黑屋子),然後通過不斷地進棧出棧實現人員順序調整,最後使得上臺之後的所有人憤怒值總和最小。
2.4 題目思路:
首先介紹一下區間DP:
定義:區間dp就是在區間上進行動態規劃,求解一段區間上的最優解。主要是通過合併小區間的 最優解進而得出整個大區間上最優解的dp演算法。
核心思路:既然讓我求解在一個區間上的最優解,那麼我把這個區間分割成一個個小區間,求解每個小區間的最優解,再合併小區間得到大區間即可。所以在程式碼實現上,我可以列舉區間長度len為每次分割成的小區間長度(由短到長不斷合併),內層列舉該長度下可以的起點,自然終點也就明瞭了。然後在這個起點終點之間列舉分割點,求解這段小區間在某個分割點下的最優解。
基本模板:
for(int len = 1;len<=n;len++){//列舉長度 for(int j = 1;j+len<=n+1;j++){//列舉起點,ends<=n int ends = j+len - 1; for(int i = j;i<ends;i++){//列舉分割點,更新小區間最優解 dp[j][ends] = min(dp[j][ends],dp[j][i]+dp[i+1][ends]+something); } } }
本題思路:
題意給出n個人,每個人都有他們自己的屌絲值,第k個人出場的憤怒值為(k-1)*屌絲值,現在有一個棧,可以通過棧的入棧出棧操作來實現調整隊伍的方法,可以很明顯的瞭解到這能夠使用區間DP,也即把一個大的區間去轉化成兩個小區間,思想有點類似於分治法,對於一個人,如果他是第k個出場的,那麼一定是i到i+k-1中以一種方式出場,然後這個人出場,然後i+k到j以一種方式出場,這樣就分成了三個區間,[i,i]、[i+1,i+k-1]、[i+k,j]這三個區間。
令 dp[i][j] 表示從 i 到 j 這些人以一種出場順序的最小值。 那麼對於剛剛劃分出來的三個區間 我們可以得到 dp[i][j] = dp[i+1][i+k-1] + (k-1)*a[i] + (sum[j]-sum[i+k-1])*k + dp[i+k][j] dp[i+1][i+k-1] :表示先出場這一批人的最小憤怒值 (k-1)*a[i] :表示 i 出場時的憤怒值 dp[i+k][j] :表示後面這一批人的最小憤怒值 (sum[j]-sum[i+k-1])*k :表示後面這一批人需要額外加上的憤怒值
2.5 題目AC程式碼:
#include<bits/stdc++.h> using namespace std; int times; int dp[1005][1005]; int main() { ios::sync_with_stdio(false); cin>>times; for(int i = 1;i<=times;i++) { int temp; cin>>temp; int *mark = new int[temp+1]; int *ans = new int[temp+1]; ans[0] = 0; for(int i = 1;i<=temp;i++) { ans[i] = 0; cin>>mark[i]; ans[i] = ans[i-1] + mark[i]; } for(int i=1;i<1005;i++) { for(int j=i+1;j<1005;j++) dp[i][j]=999999999; } for(int d=2; d<=temp; d++) { for(int i=1, j=d; j<=temp; i++, j++) { for(int k=1; k<=j-i+1; k++) { dp[i][j] = min(dp[i][j], dp[i+1][i+k-1]+dp[i+k][j]+(ans[j]-ans[i+k-1])*k+mark[i]*(k-1)); } } } cout<<"Case #"<<i<<": "<<dp[1][temp]<<endl; } }
四、 結對程式設計情況:
經過第一次實踐合作之後,第二次的實踐合作更加順利流暢,能夠讓雙方都能在相同思路上齊頭並進,當和三木小哥哥想到一起完成了演算法設計、完成程式碼書寫、成功AC一題之後,一種喜悅感湧上心頭,雖然第三題有一小點WA了,不過還是很合作默契的哈哈,感謝三木小哥哥一直包容我的錯誤哈哈哈哈。
相信在下一次結對程式設計會愈發順利,加油。
如有不合理的地方,請及時指正,我願聽取改正~
參考連結:https://oi-wiki.