动态规划(背包问题)


前言

本章开始写背包问题,具体就贴算法了,后面遇到比较特殊的题的会继续更新,这次更新就针对经典例题写一写过程和答案。


一、0-1背包问题

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 vi,价值是 wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。

输入格式 :

  1. 第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
  2. 接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式: 输出一个整数,表示最大价值。
数据范围: 0<N,V≤1000 // 0<vi,wi≤1000

#include <vector>
#include <iostream>
using namespace std;

void fun1(vector<int>& v, vector<int>& w, vector<vector<int>>& dp, int n, int m){
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            //这一行必须加上,因为有可能背包装不下,存在 j<v[i-1]的情况
            //状态转移方程,不选取第i个物品
            dp[i][j] = dp[i - 1][j];
            if(j>=v[i-1]){
            	//选取第i个物品和不选取第i个物品的最大值
                dp[i][j]=max(dp[i-1][j-v[i-1]] + w[i-1],dp[i-1][j]);
            }
        }
    }
    
    cout<<dp[n][m]<<endl;
}

void fun2(vector<int>& v, vector<int>& w, vector<int>& f, int n, int m){
    //fun1 中的更新步骤 依赖于上一轮的结果
    //我们使用下面的方法,逆序更新,
    //f[8]=max(f[3],f[3-v[0]]+w[0])  f[3] 和 f[3-v[0]] 就是上一轮的结果
    for(int i=0;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]<<endl;
}

int main(){
    int n,m;
    cin>>n>>m;
    vector<vector<int>> dp(n+1,vector<int>(m+1,0));
    vector<int> f(m+1,0);
    vector<int> v(n,0);
    vector<int> w(n,0);
    
    for(int i=0;i<n;i++) cin>>v[i]>>w[i];

    // fun1(v,w,dp,n,m);
    fun2(v,w,f,n,m);

    return 0;
}

二、完全背包问题

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。第 i 种物品的体积是 vi,价值是 wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。附原文链接

输入格式 :

  1. 第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
  2. 接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别>表示第 i 种物品的体积和价值。

输出格式:输出一个整数,表示最大价值。
数据范围:0<N,V≤1000 0<vi,wi≤1000

#include <vector>
#include <iostream>
using namespace std;

void fun1(vector<int>& v, vector<int>& w, vector<vector<int>>& dp, int n, int m){
    for(int i=1;i<=n;i++){
        for(int j=0;j<=m;j++){
            for(int k=0;k*v[i-1]<=j;k++){
                //因为 完全背包问题 不限制个数,所以需要在 k*v[i] 小于 j 的情况下也要考虑多选的情况。
                dp[i][j]=max(dp[i-1][j],dp[i-1][j-k*v[i-1]]+k*w[i-1]);
            }
        }
    }
    
    cout<<dp[n][m]<<endl;
}

void fun2(vector<int>& v, vector<int>& w, vector<int>& f, int n, int m){
    // dp[i][j] = max(dp[i-1][j], dp[i-1,j-v]+w , dp[i-1,j-2*v]+2*w,... dp[i-1,j-k*v]+k*w)
    // dp[i][j-v] = max(          dp[i-1][j-v],  dp[i-1][j-2*v]+*w,... dp[i-1,j-k*v]+(k-1)*w)
    //两个公式合并后的    dp[i][j] = max(dp[i-1][j], dp[i][j-v]+w);
    //当不选第i个物品时,dp[i][j]=dp[i-1][j], 所以上面的公式有可以更新成 dp[i][j] = max(dp[i][j], dp[i][j-v]+w);
    //i 都是当前迭代的信息,从正序读就可以正确读取信息
    for(int i=0;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]<<endl;
}

int main(){
    int n,m;
    cin>>n>>m;
    vector<vector<int>> dp(n+1,vector<int>(m+1,0));
    vector<int> f(m+1,0);
    vector<int> v(n,0);
    vector<int> w(n,0);
    
    for(int i=0;i<n;i++) cin>>v[i]>>w[i];

    //fun1(v,w,dp,n,m);
    fun2(v,w,f,n,m);

    return 0;
}

三、多重背包问题

有 N 种物品和一个容量是 V 的背包。第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。

输入格式

  1. 第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
  2. 接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

输出格式 输出一个整数,表示最大价值。

第一种问题:

  1. 数据范围 0<N,V≤100, 0<vi,wi,si≤100(普通方法)
  2. 数据范围 0<N,V≤2000, 0<vi,wi,si≤2000(二进制法)
  3. 数据范围 0<N,V≤20000, 0<vi,wi,si≤20000(单调队列法)

关于单调队列法,我把求解过程写一下,具体可以参考大佬些写的原文链接,我在代码中,加了注释。

 不放第 i 个物品  i = dp[i-1][j] 和  放k个第 i 个物品 i = dp[i-1][j - k*v] + k*w
 dp[i][j] = max(dp[i-1][j], dp[i-1][j-v] + w, dp[i-1][j-2*v] + 2*w,..., dp[i-1][j-k*v] + k*w)
 可以重复利用dp数组来保存上一轮的信息
 dp[j] = max(dp[j], dp[j-v] + w, dp[j-2*v] + 2*w, dp[j-3*v] + 3*w, ...)  
  
 接下来,我们把 dp[0] --> dp[m] 写成下面这种形式
 dp[0], dp[v],   dp[2*v],   dp[3*v],   ... , dp[k*v]
 dp[1], dp[v+1], dp[2*v+1], dp[3*v+1], ... , dp[k*v+1]
 dp[2], dp[v+2], dp[2*v+2], dp[3*v+2], ... , dp[k*v+2]
 ...
 dp[j], dp[v+j], dp[2*v+j], dp[3*v+j], ... , dp[k*v+j]

m 一定等于 k*v + j,其中  0 <= j < v
dp[k*v+j] 只依赖于 { dp[j], dp[v+j], dp[2*v+j], dp[3*v+j], ... , dp[k*v+j] }
因为我们需要的是{ dp[j], dp[v+j], dp[2*v+j], dp[3*v+j], ... , dp[k*v+j] } 中的最大值,
可以通过维护一个单调队列来得到结果。这样的话,问题就变成了 j 个单调队列的问题
所以,我们可以得到
dp[j]    =     dp[j]
dp[j+v]  = max(dp[j] +  w,  dp[j+v])
dp[j+2v] = max(dp[j] + 2w,  dp[j+v] +  w, dp[j+2v])
dp[j+3v] = max(dp[j] + 3w,  dp[j+v] + 2w, dp[j+2v] + w, dp[j+3v])

对上面的式子进行变形得:
dp[j]    =     dp[j]
dp[j+v]  = max(dp[j], dp[j+v] - w) + w
dp[j+2v] = max(dp[j], dp[j+v] - w, dp[j+2v] - 2w) + 2w
dp[j+3v] = max(dp[j], dp[j+v] - w, dp[j+2v] - 2w, dp[j+3v] - 3w) + 3w
这样,每次入队的值是 dp[j+k*v] - k*w

单调队列问题,最重要的两点
1)维护队列元素的个数,如果不能继续入队,弹出队头元素
2)维护队列的单调性,即:尾值 >= dp[j + k*v] - k*w
#include <vector>
#include <iostream>
using namespace std;

//暴力法
void fun1(){
    int n,m;
    cin>>n>>m;
    vector<int> dp(m+1,0);
    for (int i = 0; i < n; i++)
    {
        int v,w,s;
        cin>>v>>w>>s;
        for(int j=m;j>=v;j--){
            for(int k=0; k<=s && k*v<=j;k++){
                dp[j]=max(dp[j],dp[j-k*v]+k*w);
            }
        }
    }
    
    cout<< dp[m]<<endl;
    
    return 0;
}

//二进制法
void fun2(){
    int n,m;
    cin>>n>>m;
    vector<pair<int,int>> goods;
    vector<int> dp(m+1,0);
    for (int i = 0; i < n; i++)
    {
        int v,w,s;
        cin>>v>>w>>s;
        //把同一个商品给拆分成不同的个数,重组成新的“商品”,转换成0-1背包问题
        for(int k=1;k<=s;k*=2){
            s-=k;
            goods.push_back({k*v,k*w});
        }
        if(s>0) goods.push_back({s*v,s*w});
    }

    for(auto t:goods){
        for(int j=m;j>=t.first;j--){
            dp[j]=max(dp[j],dp[j-t.first]+t.second);
        }
    }
    cout<<dp[m]<<endl;
    return 0;
}


//单调队列法
int fun3(){
    int n,m;
    cin>>n>>m;
    vector<int> dp(m+1,0);
    vector<int> pre(m+1,0);
    //因为每个物品的上限是20000个
    //q队列保证每个物品被选择的次数范围是(0-s),所以数组范围是s+1
    vector<int> q(20001,0);

    for(int i=0;i<n;i++){
        pre=dp;
        //copy(dp.begin(),dp.end(),pre.begin());
        int v,w,s;
        cin>>v>>w>>s;
        for(int j=0;j<v;j++){
            // 对于每一个j,维护一个队列,重新定义head和tail可以重用之前的值
            int head=0,tail=-1;
            for(int k=j;k<=m;k+=v){
                // 每个队列保存 s+1 个元素,当超出范围时,把无用的元素踢出,
                // 需要注意一点,q保存的仅仅是 k*v + j, 位于head元素的值 已经在 dp 数组中更新 
                if(head<=tail && k-s*v>q[head]) head++;

                // pre数组存储的上一轮dp的值,pre数组将 本轮最大的值 存在队列的前面位置,即保证pre数组是单调递减的
                // dp数组实际存储的 dp[j+k*v],所以在 while 比较的时候会减去 k*w
                while (head<=tail && pre[q[tail]] - (q[tail]-j)/v*w <= pre[k]-(k-j)/v*w) tail--;

                if(head <= tail)  dp[k] = max(dp[k],pre[q[head]]+(k-q[head])/v*w);

                q[++tail]=k;
            }
        }
    }
    cout<<dp[m]<<endl;
    return 0;
}

四、混合背包问题

有 N 种物品和一个容量是 V 的背包。物品一共有三类

  1. 第一类物品只能用1次(01背包);
  2. 第二类物品可以用无限次(完全背包);
  3. 第三类物品最多只能用 si 次(多重背包);
    每种体积是 vi,价值是 wi。求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。

输入格式

  • 第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

  • 接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

  1. si=−1 表示第 i 种物品只能用1次;
  2. si=0 表示第 i 种物品可以用无限次;
  3. si>0 表示第 i 种物品可以使用 si 次;

输出格式: 输出一个整数,表示最大价值。
数据范围 0<N,V≤1000 0<vi,wi≤1000 −1≤si≤1000

#include <vector>
#include <iostream>

using namespace std;

struct good{
    int kind;
    int v,w;
};

// 如果是0-1背包 和 多重背包 就用二进制法 转换成 0-1 背包
// 如果是完全背包,就用完全背包的方法解决
// 定义一个结构体,kind=-1 代表 0-1背包,king = 0 代表完全背包
int main(){
    int n,m;
    cin>>n>>m;
    vector<int> dp(m+1,0);
    vector<good> goods;

    for(int i=0;i<n;i++){
        int v,w,s;
        cin>>v>>w>>s;
        if(s<0){
            goos.push_back({-1,v,w});
        }else if(s==0){
            goos.push_back({0,v,w});
        }else{
            for(int k=1;k<s;k*=2){
                s-=k;
                goods.push_back({-1,k*v,k*w});
            }
            if(s>0) goods.push_back({-1,s*v,s*w});
        }
    }

    for(auto s:goods){
        if(s.kind==-1){
            for(int j=m;j>=s.v;j--){
                dp[j]=max(dp[j],dp[j-s.v]+s.w);
            }
        }else if(s.kind==0){
            for(int j=s.v;j<=m;j++){
                dp[j]=max(dp[j],dp[j-s.v]+s.w);
            }
        }
    }

    cout<<dp[m];
    return 0;
}

五、二维费用背包问题

有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。

输入格式

  1. 第一行三个整数,N,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。
  2. 接下来有 N 行,每行三个整数 vi,mi,wi,用空格隔开,分别表示第 i 件物品的体积、重量和价值。

输出格式: 输出一个整数,表示最大价值。

数据范围: 0<N≤1000 0<V, M≤100 0<vi,mi≤100 0<wi≤1000

#include <vector>
#include <iostream>
using namespace std;

int main(){
    int n,v,w;
    cin>>n>>v>>w;
    vector<vector<int>> dp(v+1,vector<int>(w+1,0));

    //多了一层循环,来装第二维的费用
    for(int i=0;i<n;i++){
        int a,b,c;
        cin>>a>>b>>c;
        for(int j=v;j>=a;j--){
            for(int k=w;k>=b;k--){
                dp[j][k]=max(dp[j][k],dp[j-a][k-b]+c);
            }
        }
    }

    cout<<dp[v][w]<<endl;
    return 0;    
}

六、分组背包问题

有 N 组物品和一个容量是 V 的背包。每组物品有若干个,同一组内的物品最多只能选一个。每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。输出最大价值。

输入格式

  1. 第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。
  2. 接下来有 N 组数据:
  3. 每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量;
  4. 每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;

输出格式 输出一个整数,表示最大价值。
数据范围 0<N,V≤100 0<Si≤100 0<vij,wij≤100

#include <vector>
#include <iostream>
using namespace std;


//方法很简单
//第一步,在0-1背包基础上,加一轮循环,输入第i组的信息
//第二步,加一轮循环,在第i组选一个物品
int main(){
    int n,m;
    cin>>n>>m;
    vector<int> dp(m+1,0);
    vector<int> V(101,0);
    vector<int> W(101,0);

    for(int i=0;i<n;i++){
        int s;
        cin>>s;
        for(int j=0;j<s;j++) cin>>V[j]>>W[j];
        // j-- 注意一下
        for(int j=m;j>=0;j--){
            for(int k=0;k<s;k++)
                // 当遇到最大的值时,后保留最大值
                // 最大值只有一个,在这一组中也只会选一个物品
                // 这里注意 j>=V[k]
                if(j>=V[k]) dp[j]=max(dp[j],dp[j-V[k]]+W[k]);
        }
    }

    cout<<dp[m]<<endl;
    return 0;
}

七、有依赖的背包问题

有 N 个物品和一个容量是 V 的背包。物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。每件物品的编号是 i,体积是 vi,价值是 wi,依赖的父节点编号是 pi。物品的下标范围是 1…N。求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。输出最大价值。如下图:
在这里插入图片描述
如果选择物品5,则必须选择物品1和2。这是因为2是5的父节点,1是2的父节点。

输入格式

  1. 第一行有两个整数 N,V,用空格隔开,分别表示物品个数和背包容量。
  2. 接下来有 N 行数据,每行数据表示一个物品。 第 i 行有三个整数 vi,wi,pi,用空格隔开,分别表示物品的体积、价值和依赖的物品编号。如果 pi=−1,表示根节点。 数据保证所有物品构成一棵树。

输出格式 :输出一个整数,表示最大价值。

数据范围 :1≤N,V≤100 , 1≤vi,wi≤100
父节点编号范围:内部结点:1≤pi≤N;根节点 pi=−1;

这个题还是比较复杂的,但是如果搞清楚了树形dp和,动态规划的核心就可以了。还有一个更精简的我写在下面。

#include <vector>
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N=110;

int n,m;
//存储物品的体积和价值,以及动态规划的数组
int v[N],w[N],f[N][N];

//数组e是边的集合,下标是边的序号,值是边的终点
//数组ne是某一节点的边的集合,下标是边的序号,值是某一节点的上一条边的序号
//数组h是节点的集合,下标是节点的序号,值是最后一条边的序号
//idx是边的序号
//对应关系:通过 h[p]> 找到父节点最后一条边 idx-> ne[idx] 找到父节点的上一条边 id -> e[id] 找到父节点的孩子节点
int e[N],ne[N],h[N],idx;

void add(int p,int i){
    e[idx]=i;ne[idx]=h[p];h[p]=idx++;
}

void dfs(int u){
    for(int i=h[u];i!=-1;i=ne[i]){
        int son=e[i];
        dfs(son);
        //选择子节点就必须选择父节点,所以这里要将父节点的体积减去
        for(int j=m-v[u];j>=0;j--){
            //孩子节点的价值
            for(int k=0;k<=j;k++){
                //不选择孩子节点,自己的总价值就不会变
                //选择孩子节点,体积就会减少k,同时要加上孩子节点,体积为k是的价值    
                f[u][j]=max(f[u][j],f[u][j-k]+f[son][k]);
            }
        }
    }

    //下面两个循环就是选择第u个物品,还是不选第u个物品
    //上面的循环只是选择还是不选孩子节点
    for(int i=m;i>=v[u];i--){
        //当体积大于v[u],要将第u个物品的价值也加上
        f[u][i]=f[u][i-v[u]]+w[u];
    }

    
    for(int i=0;i<v[u];i++){
        //当体积小于v[u],放不下第u个物品,所以要置为0
        f[u][i]=0;
    }
}

int main(){
    memset(h,-1,sizeof(h));
    cin>>n>>m;
    int root;
    for(int i=1;i<=n;i++){
        int p;
        cin>>v[i]>>w[i]>>p;
        if(p==-1){
            root=i;
        }else{
            add(p,i);
        }
    }

    dfs(root);

    cout<<f[root][m]<<endl;
    return 0;
}

第二种遍历的写法,这种还是比较简洁的,但是有一点要说明一下,之前定义的树的邻接表过于复杂,大家可以直接建立一个二维数组来表示。我就不写了。原链接我给大家放这里:原链接

int dfs_1(int u){
    //当背包大于v[u]时,应该把第u个物品放进去
    for(int i=m;i>=v[u];i--) f[u][i]=w[u];
    // 这个地方一定要注意 i=ne[i];上一条边,不用写成 i=ne[u],我老是写错……
    for(int i=h[u];i!=-1;i=ne[i]){
        int son=e[i];
        dfs_1(son);
        //子树和父节点 总的空间应该是v[u]~m
        //v[u]是分给父节点的
        // j>=v[u]
        for(int j=m;j>=v[u];j--){
            //子树的空间就是j-v[u]
            for(int k=0;k<=j-v[u];k++){
                f[u][j]=max(f[u][j],f[u][j-k]+f[son][k]);
            }
        }
        
    }
}

八、背包问题求方案数

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 vi,价值是 wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出 最优选法的方案数。注意答案可能很大,请输出答案模 109+7 的结果。

输入格式

  1. 第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
  2. 接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式 输出一个整数,表示 方案数 模 10^9+7 的结果。

数据范围 0<N,V≤1000 0<vi,wi≤1000

首先我们要明确一点动态规划的数组初始化有一定的技巧:

  1. 全部初始化为0,那么 dp 数组代表当体积小于或者等于 m 时,最大价值是多少。
    因为数组 dp 全部为0,假设 dp[k] 取得最大值,那么 dp[m] 可以由 dp[m-k] 转移过来。 dp[m] 处就是最大值。
f[j]=max(f[j],f[j-v[i]]+w[i]);
  1. dp[0] 初始化为0,其余全部初始化为负无穷,那么 dp[m] 只能从 dp[0] 转移过来,dp 数组含义就代表当体积恰好为m时,最大价值是多少。 dp[m] 处不一定是最大值。因为 dp[m] 不一定会发生状态转移,其值可能是无穷小。
#include <vector>
#include <iostream>
#include <algorithm>

using namespace std;

const int N=1010, MOD=1000000007, INF=1000000;

int n,m;
int f[N],g[N];

int main(){
    for(int i=1;i<=m;i++) f[i]=-INF;
    g[0]=1;
    cin>>n>>m;
    
    for(int i=0;i<n;i++){
        int v,w;
        cin>>v>>w;
        for(int j=m;j>=v;j--){
            int t=max(f[j],f[j-v]+w);
            // 多一步,找到中间取最大值的途径,并记录下来
            int s=0;
            if(t==f[j]) s+=g[j];
            if(t==f[j-v]+w) s+=g[j-v];
            if(s>MOD) s%=MOD;
            f[j]=t;
            g[j]=s;
         }
    }
    
    int maxn=0;
    // 找到最大值
    for(int i=0;i<=m;i++) maxn=max(maxn,f[i]);
    
    int res=0;
    // 将所有可以取到最大值的途径相加
    for(int i=0;i<=m;i++){
        if(maxn == f[i]){
            res+=g[i];
            if(res>MOD) res%=MOD;
        }
    }
    
    
    cout<<res<<endl;
    return 0;
}

九、背包问题的方案

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 vi,价值是 wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输出 字典序最小的方案。这里的字典序是指:所选物品的编号所构成的序列。
物品的编号范围是 1…N。

输入格式

  1. 第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
  2. 接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式

  • 输出一行,包含若干个用空格隔开的整数,表示最优解中所选物品的编号序列,且该编号序列的字典序最小。

物品编号范围是 1…N。

数据范围 0<N,V≤1000 0<vi,wi≤1000

#include <vector>
#include <iostream>
#include <algorithm>

using namespace std;

const int N=1010;
int f[N][N];
int V[N],W[N];

int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>V[i]>>W[i];

    //之前的f[i][j] 代表背包体积为j时,前i个物品的总价值
    //为了从第一个物品开始遍历
    //我们换一下顺序,令f[i][j]代表背包体积为j时,第i个物品到最后一个物品的总价值
    for(int i=n;i>=1;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]);
        }
    }

    int vol=m;
    // 如果 f[i][vol] 由 f[i+1][vol-V[i]]+W[i] 状态转换过来
    // 说明第i个物品被选择了。
    for(int i=1;i<=n;i++){
        // 要加上 vol>=V[i] 的判断,避免越界
        if( vol >= V[i] && f[i][vol]==f[i+1][vol-V[i]]+W[i]){
            cout<<i<<" ";
            vol-=V[i];
        }
    }

    return 0;
}

总结

背包问题就写完了,有问题的欢迎留言,看的B站背包九讲,以及这个网站的练习。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值