硬幣模型
有n種硬幣,面值
硬幣模型的區別在於,這裏的起點和終點都是確定的。我們求的就是確定起點、終點的DAG最短路。
以最短路爲例,轉移方程:dp[i]=min{dp[i-V[j]]},這裏的i表示從0出發到i的最短路徑長度。
實現:
#include <iostream>
#include <climits>
#include <algorithm>
#define MAXN INT_MAX-10003
using namespace std;
int V[103],n;
int maxd[103],mind[103];
void print(int* dp,int S){
for(int i=1;i<=n;++i){
if(V[i]<=S&&dp[S]==dp[S-V[i]]+1){
cout<<i<<" ";
print(dp,S-V[i]);
break;
}
}
}
int main(){
int S;
cin>>n>>S;
for(int i=1;i<=n;++i)
cin>>V[i];
for(int i=1;i<=S;++i)
mind[i]=MAXN,maxd[i]=-MAXN;
for(int i=1;i<=S;++i){
for(int j=1;j<=n;++j){
if(i>=V[j]){
mind[i]=min(mind[i],mind[i-V[j]]+1);
maxd[i]=max(maxd[i],maxd[i-V[j]]+1);
}
}
}
if(mind[i]==MAXN&&maxd[i]==-MAXN) cout<<"Undefined"<<endl;
else{
cout<<mind[S]<<" "<<maxd[S]<<endl;
print(mind,S);
cout<<endl;
print(maxd,S);
}
}
[千萬不要太作死的用limts.h…我慘痛的+1溢出了]
[雖說劉書還提供了一種字典序打印方法…不過這裏就不列了,我覺得這種就挺好用的。]
DAG問題小結
(1)DAG問題的基本規律
DAG是有向無環圖。也就是說,對於一類可以將狀態轉換到圖上求解最短路/最長路的題目可以用這種方式解決。
動態轉移方程:
最短路:dp[i]=min{dp[j]+1} (1)
最長路:dp[i]=max{dp[j]+1} (2)
複雜度O(VE)
[可能你會奇怪,之前我們在處理硬幣問題的時候,轉移並不是長這個樣子的啊。其實不然。這裏的j,指的是頂點]
無論是(1)還是(2),本質都是一致的。但是有個問題:dp[i]的含義是什麼?有兩種情況:第一種是以i爲起點的路徑,第二種是以i爲終點的路徑。
不妨來思考一下,對於這兩種解釋,上述轉移方程是否成立…顯然是成立的。不過顯然,對於第一種情況,限制條件應該爲(j,i)
從正常情況下講,人類的思想會偏向於選擇第二種情況。事實上,這種情況寫起來也要順一些。我們兩道題都是用的這種思想。
(2)刷表法
很多時候,我們的遞推公式是建立在i與i之前狀態的。這樣我們用類似填鴉的方法,完成一張dp表格。我們稱這種方法叫填表法。
但是有個很麻煩的問題:我們並不一定能確定所有i依賴的狀態。
這時有一個看上去很蛋疼的方法:對於每個狀態i更新i影響的狀態。這樣的方法就叫刷表法。
[說實話光光是概念並不能讓我理解,然後我就百度了一下。]
http://blog.csdn.net/good_night_sion_/article/details/54868978
先貼原po,這道例題可以比較清楚的闡釋刷表法的做法。
給的幾道例題
9-1 UVa1025
看上去超級複雜渾身難受,不過其實可以意外很簡單的思考。
用dp[i][j]表示到第i時刻在車站j的最短等待時間,那麼我們求的就是起點爲0,終點爲T的DAG最短路。
因此可以寫出狀態轉移方程:
dp[i][j]=min(dp[i+1][j]+1,dp[i+t[j]][j+1],dp[i+t[j-1]][j-1])
其中取到狀態二的條件是此時有正向的車,條件三是有反向的車。
9-2 UVa437
因爲有無窮多個,所以不慫,將每個長方體變成三個。這就是一個DAG。可連通的一條路權值就是高度,求最長路即可。
說實話不是很懂劉書的dp[idx,k]…
不過其實我想問如果這裏的長方體只有一個會怎麼辦?
9-3 UVa1347
非常喪心病狂的轉移…不過一旦接受了這個設定還挺帶感的..
來換一個思考的角度,既然是一個人折返就不如看成兩個人單向。
現在的問題是,如何確定兩個人沒有經過同一個點?
不妨設現在一個人在橫座標i的點,一個在橫座標j的點。我們用dp[i][j]表示這一狀態。
想要走向下一步,有兩種轉移。一種是第一個人走向第i+1個點,一種是第二個人走向第i+1個點。轉移:
dp[i][j]=min(dp[i+1][j]+dist(i,i+1),dp[i][i+1]+dist(j,i+1))
dist表示距離,用兩點之間座標公式處理即可。
不過這樣還有些複雜。顯然,dp[i][j]=dp[j][i],因此不妨設i>j,則可以將dp[i][i+1]轉化爲dp[i+1][i]。
臨界條件爲dp[n-1][j]=dist(n-1,n)+dist(j,n)。
現在的問題只有一個:爲什麼我們的轉移只有兩種呢?明明第一個人可以走向i+1,也可以走向i+2啊!
祕訣在於:這裏的dp[i][j]是1~i全部走過(i>j),兩個人的位置爲i和j。換言之,此時如果我轉移到i+2那麼i+1就是一個未走過的部分,矛盾!這就是這個狀態設計的精妙之處。
實現可以使用記憶化搜索的方法,免去處理for方向的麻煩。