劉書學習筆記(2)DAG上dp(下)

硬幣模型

有n種硬幣,面值V1,V2,...,Vn 且有無限多個。給定S,可以選出多少硬幣,使得總額爲S?求出該數量的最大值和最小值。

硬幣模型的區別在於,這裏的起點和終點都是確定的。我們求的就是確定起點、終點的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) E,而第二種情況是(i,j) E。
從正常情況下講,人類的思想會偏向於選擇第二種情況。事實上,這種情況寫起來也要順一些。我們兩道題都是用的這種思想。
(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方向的麻煩。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章