有向無環圖上的動態規劃是學習動態規劃的基礎,很多問題都可以轉化為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中。
記憶化搜尋程式碼如下:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define maxn 1000
using namespace std;
int G[maxn][maxn], a[maxn], b[maxn], d[maxn], n, answer;
int dp(int i)
{
int& ans = d[i];
if (ans > ) return ans;
ans = ;
for(int j = ; j <= n; ++j) if(G[i][j]) ans = max(ans, dp(j)+);
return ans;
}
void print_ans(int i)
{
printf("%d(%d, %d) ", i, a[i], b[i]);
for(int j = ; j <= n; ++j) if(G[i][j] && d[j]+ == d[i])
{
print_ans(j);
break;
}
} int path[maxn] = {}, cnt = ;
void print_all(int i)
{
//輸出所有符合條件的路徑
path[++cnt] = i;
if(cnt == answer)
{
for(int j = ; j <= cnt; ++j) printf("%d(%d, %d) ", path[j], a[path[j]], b[path[j]]);
printf("\n");
}
else for(int j = ; j <= n; ++j) if(G[i][j] && d[j]+ == d[i])
{
print_all(j);
}
--cnt;
}
/*
//作者的方法
int path[maxn];
void print_all(int cur, int i) {
path[cur] = i;
if(d[i] == 1) {
for(int j = 0; j <= cur; j++) printf("%d ", path[j]);
printf("\n");
}
for(int j = 1; j <= n; j++) if(G[i][j] && d[i] == d[j]+1)
print_all(cur+1, j);
}
*/
int main()
{
freopen("9-2.in", "r", stdin);
scanf("%d", &n);
for(int i = ; i <= n; ++i) scanf("%d%d", a+i, b+i);
memset(G, , sizeof(G));
for(int i = ; i <= n; ++i)
for(int j = ; j <= n; ++j) if((a[i]>a[j]&&b[i]>b[j])||(a[i]>b[j]&&b[i]>a[j]))
G[i][j] = ;
int t, s;
answer = -;
memset(d, , sizeof(d));
for (int i = ; i <= n; ++i)
{
t = dp(i);
if(answer < t)
{
answer = t;
s = i;
}
}
printf("%d\n", answer);
print_ans(s);
printf("\nAll routes:\n");
//print_all(0, s);
print_all(s);
return ;
}
另一程式碼如下:
//另附0ms 236kb的DP思路:按邊長降序排序,用類似LIS的方法求解,只是比較元素大小的方法變成了比較長和寬
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define maxn 1008
using namespace std;
int G[maxn][maxn], a[maxn], b[maxn], d[maxn], n;
int dp(int i)
{
int& ans = d[i];
if (ans > ) return ans;
ans = ;
for(int j = ; j <= n; ++j) if(G[i][j]) ans = max(ans, dp(j)+);
return ans;
}
int main()
{
int t;
scanf("%d", &t);
while(t--)
{
scanf("%d", &n);
for(int i = ; i <= n; ++i) scanf("%d%d", a+i, b+i);
memset(G, , sizeof(G));
for(int i = ; i <= n; ++i)
for(int j = ; j <= n; ++j) if((a[i]>a[j]&&b[i]>b[j])||(a[i]>b[j]&&b[i]>a[j]))
G[i][j] = ;
int ans = -, t, s;
memset(d, , sizeof(d));
for (int i = ; i <= n; ++i)
{
t = dp(i);
if(ans < t)
{
ans = t;
s = i;
}
}
printf("%d\n", ans);
}
return ;
}