DAG模型——巢狀矩陣

賈樹丙發表於2013-09-06

  有向無環圖上的動態規劃是學習動態規劃的基礎,很多問題都可以轉化為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 }

 

 

 

相關文章