$E=mc^{2}$
很多問題都可以轉化為DAG上的最長(短)路路徑,最多(少)路徑數(路徑的權值為1)
對於狀態d[i]的設置可以有兩種:
1.d[i]表示從i出發的最長路
一般這種時候會考慮打印路勁,在出發之后會同時用一個數組來記錄路徑,而且這種方式一般通過回溯找到最大值。這種方法多數情況下不被推薦使用(偶爾用來取巧)(沒有固定起點終點的情況下找到從任意點出發的總長並且可以記錄從這個點出發到底端一步一步的路徑),因為一般情況下都會有一個終止狀態,用這種方法很難找到終止狀態。
2.d[i]表示以i結束的最長路
缺點有時候很容易枚舉從某個結點i出發到達的所有邊(i,j),卻不容易枚舉(j,i),即所有到達j的結點(因為是有向圖),對應的過程不可逆。傳統遞推是對於每個i,所有邊。而解決該問題的辦法是,對於每個結點i,枚舉(i,j),但是操作對象確實對j,對j重復操作更新d[i]=max(d[j],d[i]+1)。這個式子會在滿足狀態轉移的情況下,重復更新。
下面可以考慮兩個問題:
(1)不固定起點終點的DAG最長路徑
(2)固定終點的最長路徑
(1)這是可以用1完成的問題
1 void DP(int a){ 2 if(visited[a]) 3 return d[a]; 4 visited[a] = true; 5 for(int i=1;i<=n;i++){ 6 if(edge[a][i]){//表示有向聯通 7 //d[a] = max(d[a],d[i]+edge[a][i]); //不用加入路徑就這樣 8 //假如要加入路徑 9 if(d[a]<D(i)+edge[a][i]){ 10 d[a] = D(i)+edge[a][i]; 11 next[a] = i; //每次都會更新,直到最小,可以用於打印路徑 12 } 13 } 14 } 15 return d[a]; 16 }
這個dp只能從這個點有向方向出去進行DFS然后回溯,而不能搜到它的上級,所以很多題目要對所有點搜一次,當重復搜索的時候,只用return一個值不會占用很多時間。而且這個可以保證得到的最長路徑的情況下(假如有多個相同的最大值),序號和是最小的。因為每次都是從較小序號開始回溯,而且只有更大的情況下才會更新,所以能保證最小。
for(int i=1;i<=n;i++) DP(i);
如果要用2完成,可以確定某一條最大路徑,但是比較難得到相長度同路徑中的最小(大)序列。原因見(2)中代碼。
(2)因為要固定了終點,所以用1處理可能會比較麻煩(大多數情況下都是用2),因為不知道是否最大值是到這個終點。所以設置d[i]為到達i時的最大值。
如果是要固定起點固定終點,初始化起點位置為0就可以了,然后把無關的邊全部去掉,清理掉圖來變成只固定終點。
1 void DP(int a){ 2 if(visited[a]) 3 return d[a]; 4 visited[a] = true; 5 for(int i=1;i<=n;i++){ 6 if(edge[i][a]){ 7 //d[a] = max(d[a],DP(i)+edge[i][a]); 8 //感覺也可以加入路徑 9 if(d[a]<DP(i)+edge[i][a]){ 10 d[a] = DP(i)+edge[i][a]; 11 before[a] = i; //也會有狀態更新 //能否通過這個判斷最小序列?不行,它同樣是從最后一位開始取最優的 12 } 13 } 14 } 15 return d[a]; 16 }
可以打印路徑但是不能打印序列最小
1 void print_ans(int i){ //逆序打印,可以多加一個數組來變成正序 2 printf("%d",i); 3 for(int j=1;j<=n;j++){ 4 if(d[i]==d[j]+edge[i][j]){ 5 printf_ans(j); 6 break; //防止多打東西 7 } 8 } 9 }
這種方法無法打印最小序列,(最小序列:比如出現順序是1 2 3 5,則為 1235,出現順序為2 1 3 4,則為2134),因為它是從后往前面判斷的,無法(判斷)每次取最前面位的最小情況,因為每一位的權重不一樣,越前面權重越大,但是反而在后面取到最優情況可能前面就取不到了。