有向無環圖上的動態規劃是學習動態規劃的基礎,很多問題都可以轉化為DAG上的最長路、最短路或路徑計數問題。
巢狀矩陣
有n個矩陣,每個矩陣可以用兩個整數a,b描述,表示它的長和寬。矩陣X(a,b)可以巢狀在矩陣Y(c,d)中當且僅當a<c,b<d,或者b<c,a<d(相當於把矩陣X旋轉90。)例如(1,5)可以巢狀在(6,2)內,但不能巢狀在(3,4)內。你的任務是選出儘量多的矩陣排成一行,使得除了最後一個只之外,每一個矩形都可以巢狀在下一個矩形內。
分析:
矩陣之間的“可巢狀”關係是一個典型的二元關係,二元關係可以用圖來建模。如果矩形X可以巢狀在矩形Y裡,我們就從X到Y連一條有向邊,這個圖是無環的,因為一個矩形無法直接或者間接地巢狀在自己內部。換句話說,它是一個DAG,我們的任務便是求DAG上的最長路徑。
設d(i)表示從結點i 出發的最長路長度。第一步只能到它的相鄰點,d(i) = max{d(j)+1|(i,j)屬於E},其中E是邊集。最終答案是所有d(i)中的最大值。假設用鄰接矩陣儲存在矩陣G中。
記憶化搜尋程式碼如下:
1 #include<cstdio> 2 #include<cstdlib> 3 #include<cstring> 4 #include<algorithm> 5 #define maxn 1000 6 using namespace std; 7 int G[maxn][maxn], a[maxn], b[maxn], d[maxn], n, answer; 8 int dp(int i) 9 { 10 int& ans = d[i]; 11 if (ans > 0) return ans; 12 ans = 1; 13 for(int j = 1; j <= n; ++j) if(G[i][j]) ans = max(ans, dp(j)+1); 14 return ans; 15 } 16 void print_ans(int i) 17 { 18 printf("%d(%d, %d) ", i, a[i], b[i]); 19 for(int j = 1; j <= n; ++j) if(G[i][j] && d[j]+1 == d[i]) 20 { 21 print_ans(j); 22 break; 23 } 24 } 25 26 int path[maxn] = {0}, cnt = 0; 27 void print_all(int i) 28 { 29 //輸出所有符合條件的路徑 30 path[++cnt] = i; 31 if(cnt == answer) 32 { 33 for(int j = 1; j <= cnt; ++j) printf("%d(%d, %d) ", path[j], a[path[j]], b[path[j]]); 34 printf("\n"); 35 } 36 else for(int j = 1; j <= n; ++j) if(G[i][j] && d[j]+1 == d[i]) 37 { 38 print_all(j); 39 } 40 --cnt; 41 } 42 /* 43 //作者的方法 44 int path[maxn]; 45 void print_all(int cur, int i) { 46 path[cur] = i; 47 if(d[i] == 1) { 48 for(int j = 0; j <= cur; j++) printf("%d ", path[j]); 49 printf("\n"); 50 } 51 for(int j = 1; j <= n; j++) if(G[i][j] && d[i] == d[j]+1) 52 print_all(cur+1, j); 53 } 54 */ 55 int main() 56 { 57 freopen("9-2.in", "r", stdin); 58 scanf("%d", &n); 59 for(int i = 1; i <= n; ++i) scanf("%d%d", a+i, b+i); 60 memset(G, 0, sizeof(G)); 61 for(int i = 1; i <= n; ++i) 62 for(int j = 1; j <= n; ++j) if((a[i]>a[j]&&b[i]>b[j])||(a[i]>b[j]&&b[i]>a[j])) 63 G[i][j] = 1; 64 int t, s; 65 answer = -1; 66 memset(d, 0, sizeof(d)); 67 for (int i = 1; i <= n; ++i) 68 { 69 t = dp(i); 70 if(answer < t) 71 { 72 answer = t; 73 s = i; 74 } 75 } 76 printf("%d\n", answer); 77 print_ans(s); 78 printf("\nAll routes:\n"); 79 //print_all(0, s); 80 print_all(s); 81 return 0; 82 }
另一程式碼如下:
1 //另附0ms 236kb的DP思路:按邊長降序排序,用類似LIS的方法求解,只是比較元素大小的方法變成了比較長和寬 2 #include<cstdio> 3 #include<cstdlib> 4 #include<cstring> 5 #include<algorithm> 6 #define maxn 1008 7 using namespace std; 8 int G[maxn][maxn], a[maxn], b[maxn], d[maxn], n; 9 int dp(int i) 10 { 11 int& ans = d[i]; 12 if (ans > 0) return ans; 13 ans = 1; 14 for(int j = 1; j <= n; ++j) if(G[i][j]) ans = max(ans, dp(j)+1); 15 return ans; 16 } 17 int main() 18 { 19 int t; 20 scanf("%d", &t); 21 while(t--) 22 { 23 scanf("%d", &n); 24 for(int i = 1; i <= n; ++i) scanf("%d%d", a+i, b+i); 25 memset(G, 0, sizeof(G)); 26 for(int i = 1; i <= n; ++i) 27 for(int j = 1; j <= n; ++j) if((a[i]>a[j]&&b[i]>b[j])||(a[i]>b[j]&&b[i]>a[j])) 28 G[i][j] = 1; 29 int ans = -1, t, s; 30 memset(d, 0, sizeof(d)); 31 for (int i = 1; i <= n; ++i) 32 { 33 t = dp(i); 34 if(ans < t) 35 { 36 ans = t; 37 s = i; 38 } 39 } 40 printf("%d\n", ans); 41 } 42 return 0; 43 }