1. 程式人生 > >動態規劃_DAG模型

動態規劃_DAG模型

DAG(有向無環圖)上的動態規劃是學習動態規劃的基礎。

  • 最長路及其字典序
  • 固定終點的最長路和最短路

*1.巢狀矩形問題*

有n個矩形,每個矩形可以用a,b來描述,表示長和寬。矩形X(a,b)可以巢狀在矩形Y(c,d)中當且僅當a < c,b < d或者b < c,a < d(相當於旋轉X90度)。例(1,5)可以巢狀在(6,2)內,但不能巢狀在(3,4)中。你的任務是選出儘可能多的矩形排成一行,使得除最後一個外,每一個矩形都可以巢狀在下一個矩形內。

—————————————————————
得動態轉移方程:d(i)=max{d(j)+1 | (i,j)∈E}
其中E為邊集。求解:
—————————————————————

//現假設鄰接矩陣已求出,存放在矩陣G中。
int dp(int i) {
    int& ans = d[i];//引用,方便對於d[i][j][k][l][m][n]的書寫
    if(ans > 0) return ans;//若已賦值,無需再次計算
    ans = 1;
    for(int i=0; j<=n; j++)
        if(G[i][j])/*以構建DAG*/ ans = max(ans, dp(j)+1);
    return ans;
//列印輸出
void print_ans(int i) {
    printf("%d", i);
    for
(int j=1; j<=n; j++) if(G[i][j] && d[i]==d[j]+1) { print_ans(j); break; } }

*2.硬幣問題(完全揹包問題)*

n種硬幣,面值分別為V1,V2,…Vn,每種都有無限多。給定非負整數S,可以選用多少個硬幣,使得面值之和恰好為S?輸出硬幣數目的最小值和最大值。

//最長路的程式碼 | 【初始化】mamset(d, -1, sizeof(d)); | (有瑕疵)
int dp(int S) {
    int& ans = d[S];
    if
(ans>=0) return ans; ans = 0; for(int i=0; i<=n; i++) if(S>=V[i]) ans = max(ans, dp(S-V[i])+1); return ans; } //【BUG】S不一定可到達0
//【修正】
int dp(int S) {
    int& ans = d[S];
    if(ans!=-1) return ans;//已賦值
    ans = -(1<<30);
    for(int i=0; i<=n; i++)
        if(S>=V[i]) ans = max(ans, dp(S-V[i])+1);
    return ans;
}
//【TIPS】用特殊值(如-1)表示"未算過",則必須將其和其他特殊值(如無解)
//區分開。求最大值時最好將初值設為"無窮小"。
//【可讀性優化】用vis陣列記錄訪問狀態。用空間代價增強程式碼可讀性,減少程式碼出錯可能性。
int dp(int S) {
    if(vis[S]) return d[S];
    vis[S] = 1;
    int &ans = d[S];
    ans = -(1<<30);
    for(int i=0; i<=n; i++)
        if(S>=V[i]) ans = max(ans, dp(S-V[i])+1);
    return ans;
}

【遞推】注意計算順序和邊界處理

minv[0] = maxn[0] = 0;
for(int i=1; i<=S; i++) {
    minv[i] = INF; maxv[i] = -INF;
}
for(int i=1; i<=S; i++)
    for(int j=1; j<=n; j++)
        if(i>=V[j]) {
            minv[i] = min(minv[i], minv[i-V[j]]+1);
            maxv[i] = max(maxv[i], maxv[i-V[j]]+1);
        }
printf("%d %d\n", minv[S], maxv[S]);

輸出字典序最小的方案(狀態的可逆)

//【遞迴列印】
void print_ans(int* d, int S) {
    for(int i=1; i<=n; i++)
        if(S>=V[i] && d[S]==d[S-V[i]]+1) {
            printf("%d ", i);
            print_ans(d,  S-V[i]);
            break;
        }   
}
//【遞推列印】
void print_ans(int* d, int S) {
    while(S) {
        printf("%d ", d[S]);
        S -= V[d[S]];
    }
}


for(int i=1; i<=S; i++)
    for(int j=1; i<=n; j++)
        if(i>=V[j]) {
            if(min[i]>min[i-V[j]]+1) {
                min[i]=min[i-V[j]+1;//更新min,尋找最小的min
                min_coin[i]=j;//選擇第j種硬幣
            }
            if(max[i]<max[i-V[j]]+1) {
                max[i]=max[i-V[j]+1;
                max_coin[i]=j;
            }
        }
print_ans(min_coin, S);
print_ans(max_coin, S);

對於某些不可逆的問題:

  • “填表法”。對於每個狀態i,找到f(i)依賴的所有狀態。
  • “刷表法”。對於每個狀態i,更新f(i)所影響到的狀態。