动态规划_DAG模型

DAG(有向无环图)上的动态规划是学习动态规划的基础。

  • 最长路及其字典序
  • 固定终点的最长路和最短路

【嵌套矩形问题】

有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;
        }
}

【硬币问题(完全背包问题)】

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)所影响到的状态。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值