AcWing 算法基础课笔记 5.动态规划(持续更新)


背包问题

01背包

主要思想

DP主要从两个角度进行考虑:

  1. 状态表示 f ( i , j ):用几维来表示状态,每一个状态的含义是什么。
    1. 集合:是所有选法的集合,选择过程有两个条件:
      1. 只能从前 i 个物品中选
      2. 当前总体积 <= j
    2. 属性:最大值max,最小值min,元素数量
  2. 状态计算:如何一步步把每一个状态算出来
    一般来说,对应的是集合的划分。例如 在算到某一个物品 i 时,将集合划分为两类:
    一类包含 i :最大值为 f ( i - 1 , j - vi) + wi
    一类不包含 i :最大值为 f ( i - 1 , j )
    所以最终的 f ( i , j ) 为:上面二者取 max。

在这里插入图片描述

DP的优化一般都是对DP问题的代码或计算方程进行变形。

例题

在这里插入图片描述

解1(二维):

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;
int n, m;
int v[N], w[N];
int f[N][N];

int main(){
    cin>>n>>m;
    for(int i = 1; i <= n; i ++) cin>>v[i]>>w[i];
    
    for(int i = 1; i <= n; i ++){
        for(int j = 0; j <= m; j++){
            f[i][j] = f[i - 1][j];
            if(j >= v[i]) f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
        }
    }
    
    cout<<f[n][m];
    
    return 0;
}

解2(优化,滚动数组,一维):

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;
int n, m;
int v[N], w[N];
int f[N];

int main(){
    cin>>n>>m;
    for(int i = 1; i <= n; i ++) cin>>v[i]>>w[i];
    
    for(int i = 1; i <= n; i ++)
        for(int j = m; j >= v[i]; j--)
            f[j] = max(f[j], f[j - v[i]] + w[i]);
    
    cout<<f[m];
    
    return 0;
}

完全背包

主要思想

主要从两个角度进行考虑:

  1. 状态表示 f ( i , j ):用几维来表示状态,每一个状态的含义是什么。
    1. 集合:是所有选法的集合,选择过程有两个条件:
      1. 只能从前 i 个物品中选
      2. 当前总体积 <= j
    2. 属性:最大值max,最小值min,元素数量
  2. 状态计算:如何一步步把每一个状态算出来
    一般来说,对应的是集合的划分。例如 在算到某一个物品 i 时,将集合划分为 k (k 为第 i 个物品的数量)类:
    包含 k 个 i :最大值为 f ( i - 1 , j - k * vi) + k * wi
    不包含 i :最大值为 f ( i - 1 , j )
    所以最终的 f ( i , j ) 为:上面取 max。
    在这里插入图片描述

DP的优化一般都是对DP问题的代码或计算方程进行变形。

例题

在这里插入图片描述
解1(朴素做法,会超时):

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;
int n, m;
int v[N],w[N];
int f[N][N];

int main(){
    cin>>n>>m;
    
    for(int i = 1; i <= n; i++) cin>>v[i]>>w[i];
    
    for(int i = 1; i <= n; i++){
        for(int j = 0; j <= m; j++){
            for(int k = 0; k * v[i] <= j; k++){
                f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
            }
        }
    }
    
    cout<<f[n][m];
    
    return 0;
}

解2(优化为二维):
根据 f [ i ][ j ] 与 f [ i ][ j - v ] 的 max 值的相似性,可以将状态转移方程优化为:f [ i ][ j ] = max(f [ i - 1 ][ j ], f [ i ][ j - v ] + w)
这样就不需要进行第三重循环中的 k 项枚举,降为二维。
在这里插入图片描述

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;
int n, m;
int v[N],w[N];
int f[N][N];

int main(){
    cin>>n>>m;
    
    for(int i = 1; i <= n; i++) cin>>v[i]>>w[i];
    
    for(int i = 1; i <= n; i++){
        for(int j = 0; j <= m; j++){
            f[i][j] = f[i - 1][j];
            if(j >= v[i]) f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i]);
        }
    }
    
    cout<<f[n][m];
    
    return 0;
}

解3(一维):
根据 01 背包和完全背包状态转移方程的相似性,所以完全背包也可以降为1维。
在这里插入图片描述

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;
int n, m;
int v[N],w[N];
int f[N];

int main(){
    cin>>n>>m;
    
    for(int i = 1; i <= n; i++) cin>>v[i]>>w[i];
    
    for(int i = 1; i <= n; i++){
        for(int j = v[i]; j <= m; j++){
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }
    
    cout<<f[m];
    
    return 0;
}

多重背包

主要思想

与完全背包基本相同,主要从两个角度进行考虑:

  1. 状态表示 f ( i , j ):用几维来表示状态,每一个状态的含义是什么。
    1. 集合:是所有选法的集合,选择过程有两个条件:
      1. 只能从前 i 个物品中选
      2. 当前总体积 <= j
    2. 属性:最大值max,最小值min,元素数量
  2. 状态计算:如何一步步把每一个状态算出来
    一般来说,对应的是集合的划分。例如 在算到某一个物品 i 时,将集合划分为 k (k 为第 i 个物品的数量,最大为 si)类:
    包含 k 个 i :最大值为 f ( i - 1 , j - k * vi) + k * wi
    不包含 i :最大值为 f ( i - 1 , j )
    所以最终的 f ( i , j ) 为:上面取 max。
    在这里插入图片描述

例题

在这里插入图片描述
解(朴素做法):

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;
int n, m;
int v[N], w[N], s[N];
int f[N][N];

int main(){
    cin>>n>>m;
    for(int i = 1; i <= n; i++) cin>>v[i]>>w[i]>>s[i];
    
    for(int i = 1; i <= n; i++){
        for(int j = 0; j <= m; j++){
            for(int k = 0; k <= s[i] && k * v[i] <= j; k++){
                f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
            }
        }
    }
    
    cout<<f[n][m];
    
    return 0;
}

在这里插入图片描述
解(二进制优化):

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 25000;
int n, m;
int v[N], w[N];
int f[N];

int main(){
    cin>>n>>m;
    
    int cnt = 0;
    for(int i = 1; i <= n; i++){
        int a, b, s;
        cin>>a>>b>>s;
        int k = 1;
        while(k <= s){
            cnt++;
            v[cnt] = k * a;
            w[cnt] = k * b;
            s -= k;
            k *= 2;
        }
        if(s > 0){
            cnt++;
            v[cnt] = a * s;
            w[cnt] = b * s;
        }
    }
    
    n = cnt;
    
    for(int i = 1; i <= n; i++){
        for(int j = m; j >= v[i]; j --){
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }
    
    cout<<f[m];
    
    return 0;
}

分组背包

主要思想

与完全背包基本相同,主要从两个角度进行考虑:

  1. 状态表示 f ( i , j ):用几维来表示状态,每一个状态的含义是什么。
    1. 集合:是所有选法的集合,选择过程有两个条件:
      1. 只能从前 i 个物品中选
      2. 当前总体积 <= j
    2. 属性:最大值max,最小值min,元素数量
  2. 状态计算:如何一步步把每一个状态算出来
    一般来说,对应的是集合的划分。例如 在算到某一个物品 i 时,将集合划分为 k (k 为第 i 个物品的数量,最大为 si)类:
    包含第 k 个 i :最大值为 f ( i - 1 , j - vi , k) + wi , k
    不包含 i :最大值为 f ( i - 1 , j )
    所以最终的 f ( i , j ) 为:上面取 max。
    在这里插入图片描述

例题

在这里插入图片描述
解1:

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 110;
int n, m;
int v[N][N], w[N][N], s[N];
int f[N][N];

int main(){
    cin>>n>>m;
    
    for(int i = 1; i <= n; i++){
        cin>>s[i];
        for(int j = 0; j < s[i]; j++){
            cin>>v[i][j]>>w[i][j];
        }
    }
    
    for(int i = 1; i <= n; i++){
        for(int j = 0; j <= m; j++){
            for(int k = 0; k <= s[i]; k++){
                if(v[i][k] <= j)
                f[i][j] = max(f[i][j], f[i - 1][j - v[i][k]] + w[i][k]);
            }
        }
    }
    
    cout<<f[n][m];
    
    return 0;
}

解2(优化):

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 110;
int n, m;
int v[N][N], w[N][N], s[N];
int f[N];

int main(){
    cin>>n>>m;
    
    for(int i = 1; i <= n; i++){
        cin>>s[i];
        for(int j = 0; j < s[i]; j++){
            cin>>v[i][j]>>w[i][j];
        }
    }
    
    for(int i = 1; i <= n; i++){
        for(int j = m; j >= 0; j--){
            for(int k = 0; k <= s[i]; k++){
                if(v[i][k] <= j)
                f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
            }
        }
    }
    
    cout<<f[m];
    
    return 0;
}

线性DP

区间DP


有些较复杂的懒得写特别细,建议AcWing学一下y总的课效果更好。
以上截图和模板均来源:AcWing
链接:https://www.acwing.com/blog/content/404/

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

元子方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值