zoukankan      html  css  js  c++  java
  • 背包九讲整理

    背包九讲类型汇总:

    1.01背包问题

    2.完全背包问题

    3.多重背包问题

    4.混合背包问题

    5.二维费用的背包问题

    6.分组背包问题

    7.有依赖的背包问题

    8.背包问题求方案数

    9.求背包问题的具体方案

    1. 01背包问题 Acwing 02

    有 N 件物品和一个容量是 V的背包。每件物品只能使用一次。

    第 i件物品的体积是 vi,价值是 wi

    求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
    输出最大价值。

    朴素版解法:二维空间解法
    每件物品只能选一次,对于每种物品,我们有两种选择

    1.不选 -> dp[i][j]=dp[i-1][j]
    等于选前i-1个物品,空间为j情况下的最优解
    2.选 -> dp[i][j]=dp[i-1][j-v[i]]+w[i]
    如果选的话,前i-1个物品的体积最多为j-v[i]

    #include<iostream>
    #include<cstring>
    using namespace std;
    int main(){
        int n, V;
        cin >> n >> V;
        int v[n+1],w[n+1];
        for(int i = 1; i <= n; i++)
            cin >> v[i] >> w[i];
        int dp[n+1][V+1];////dp[i][j]表示前i个物品,背包容量是j的情况下的最大价值。
        memset(dp,0,sizeof(dp));
        for(int i = 1;i <=n; i++)
            for(int j = 1; j <= V; j++)
                if(v[i] <= j) dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
                else dp[i][j] = dp[i-1][j];//这句容易忘
        cout <<dp[n][V];
    }

    解法二:滚动数组优化:(实际上只需要一个数组)

    #include<iostream>
    #include<cstring>
    using namespace std;
    int main(){
        int n, V;
        cin >> n >> V;
        int v[n+1],w[n+1];
        for(int i = 1; i <= n; i++)
            cin >> v[i] >> w[i];
        int dp[V+1];////dp[j]表示背包容量是j的情况下的最大价值。
        memset(dp,0,sizeof(dp));
        for(int i = 1;i <=n; i++)
            for(int j = V;j>= v[i];j--)
                dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
        cout <<dp[V];
    }

    注:若题目要求装满背包,即将物品恰装入一个容量为m的背包中,只需要将初始化条件改一改即可,----将dp数组初始化为负无穷,dp[0]=0,即可确保状态一定是从0转移过来的。

    2.完全背包问题 Acwing 03

    有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。

    第 i 种物品的体积是 vi,价值是 wi。

    求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
    输出最大价值。

    朴素版解法:二维空间解法
    也是两种选择,选或不选,只不过每个物品可以选无限次,在01的基础上把
    dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i])
    改为
    dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]) (dp[i][j-v[i]] 可能已经是选过第i个的了)即可

    #include<iostream>
    #include<cstring>
    using namespace std;
    int main(){
        int n, V;
        cin >> n >> V;
        int v[n+1],w[n+1];
        for(int i = 1; i <= n; i++)
            cin >> v[i] >> w[i];
        int dp[n+1][V+1];////dp[i][j]表示前i个物品,背包容量是j的情况下的最大价值。
        memset(dp,0,sizeof(dp));
        for(int i = 1;i <=n; i++)
            for(int j = 1; j <= V; j++)
                if(v[i] <= j) dp[i][j] = max(dp[i-1][j],dp[i][j-v[i]]+w[i]); //注意
                else dp[i][j] = dp[i-1][j];
        cout <<dp[n][V];
    }

    优化空间版解法:
    转移方程为dp[j]=max(dp[j],dp[j-v[i]]+w[i])

    #include<iostream>
    #include<cstring>
    using namespace std;
    int main(){
        int n,m;
        cin >> n >>m;
        int v[n+1],w[n+1];
        for(int i = 1; i <= n; i++) cin >> v[i] >> w[i];
        int dp[m+1];
        memset(dp,0,sizeof(dp));
        for(int i = 1; i <= n; i++)
            for(int j = v[i]; j <= m; j++)//注意
                dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
        cout << dp[m];
    }

    3.多重背包问题

    方法1:o(n^3)做法 Acwing 04

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

    第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi

    求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
    输出最大价值。

    数据范围

    0<N,V1000<N,V≤100
    0<vi,wi,si100

    思路分析

    多重背包是选0个,1个,2个…s[i]个
    即dp[j]=max(dp[j],dp[j - v[i] * k]+w[i] * k)
    k=1,2,3,…s[i]
    那么再加一层循环表示选多少个就可以了

    #include<iostream>
    #include<cstring>
    using namespace std;
    int main(){
        int v[104],s[104],w[104];
        int n, m;
        cin >> n >> m;
        for(int i = 1; i <=n;i++)
            cin >> v[i] >> w[i] >> s[i];
        int dp[104];
        memset(dp,0,sizeof(dp));
        for(int i=1; i <= n; i++)
            for(int j = m; j >= v[i];j--){
                for(int k = 1; j-k*v[i]>= 0 && k <= s[i]; k++)//注意条件
                    dp[j] = max(dp[j],dp[j-k*v[i]]+k*w[i]);
            }
        cout << dp[m];
    }

    方法2:二进制优化做法 Acwing 05

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

    第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。

    求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
    输出最大价值。

    数据范围:

    0<N10000<N≤1000
    0<V20000<V≤2000
    0<vi,wi,si2000

    分析:O(n^3)的方法肯定会超时,需要(N^2*logN)的方法;

    把si看做是一个二进制数,把si个可以拆成logsi个物体,这些物体肯定可以组成si个物体,转化成01背包问题;

    #include<iostream>
    #include<vector>
    using namespace std;
    #define N 2004
    int v[N],w[N],s[N];
    int main(){
        vector<int> vv = {0};
        vector<int> ww = {0};
        int n, m;
        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 k = 1; k <= s[i];k*=2){
                vv.push_back(k*v[i]);
                ww.push_back(k*w[i]);
                s[i] -= k;
            }
            if(s[i]) {
                vv.push_back(s[i]*v[i]);
                ww.push_back(s[i]*w[i]);
            }
        }
        //新物品的个数
        n = vv.size()-1;
        //常规01背包问题
        vector<int> dp(m+1);
        for(int i = 1; i<= n; i++){
            for(int j = m; j >= vv[i];j--){
                dp[j] = max(dp[j],dp[j-vv[i]]+ww[i]);
            }
        }
        cout << dp[m];
        
    }

    题目3:多重背包终极版… Acwing 06

    题目跟上面一样,但是数据范围如下
    在这里插入图片描述

    4.混合背包问题 Acwing 07

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

    物品一共有三类:

    第一类物品只能用1次(01背包);
    第二类物品可以用无限次(完全背包);
    第三类物品最多只能用 si 次(多重背包);
    每种体积是 vi,价值是 wi。

    求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
    输出最大价值。

    #include<iostream>
    #include<vector>
    using namespace std;
    #define N 1004
    int main(){
        vector<int> v(N),w(N),s(N);
        int m,n;
        cin >> n >> m;
        for(int i = 1; i <= n; i++){
            cin >> v[i] >> w[i] >> s[i];
            if(s[i] == -1) s[i] = 1;//01背包相当于只能选一次的多重背包
        }
        vector<int> dp(N);
        for(int i = 1; i <= n; i++){
            if(s[i] == 0){//可以选无数次
                for(int j = v[i];j <= m; j++)
                    dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
            } 
            else if(s[i] > 0){//多重背包问题,二进制优化,优化时直接计算
                for(int k = 1; k <= s[i]; k*=2){
                    s[i] -= k;
                    for(int j = m; j >= k*v[i];j--)
                        dp[j] = max(dp[j],dp[j-k*v[i]]+k*w[i]);
                }
            }
            //多重背包最后一个,或者01背包
            for(int j = m; j >= s[i]*v[i];j--)
                dp[j] = max(dp[j],dp[j-s[i]*v[i]]+s[i]*w[i]);
        }
        cout << dp[m];
        
    }

    5.二维费用的背包问题 Acwing 08

    有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。

    每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。

    求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
    输出最大价值。

    分析:在一维的基础上加一重循环即可;

    #include<iostream>
    #include<vector>
    using namespace std;
    int main(){
        int N,M,V;
        cin >> N >> V >> M;
        vector<int> v(N+1),m(N+1),w(N+1);
        vector<vector<int>> dp(M+1,vector<int>(V+1));
        for(int i = 1; i <= N; i++){
            cin >> v[i] >> m[i] >> w[i];
        }
        for(int i = 1; i <= N; i++){
            for(int j = M; j >= m[i]; j--)
                for(int k = V;k >= v[i]; k--)
                    //是否选第i个物品
                    dp[j][k] = max(dp[j][k],dp[j-m[i]][k-v[i]] + w[i]);
        }
        cout << dp[M][V];
    }

    6.分组背包问题 Acwing 09

    有 N 组物品和一个容量是 V 的背包。

    每组物品有若干个,同一组内的物品最多只能选一个。
    每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。

    求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。

    输出最大价值。

    思路:和多重背包有一些类似,多重背包是每个物品有si件,可以选0,1,2…si件。

    而分组背包是不选,选第1个,或第2个或第3个…或第si个,都有si+1种决策方式,

    即使用三层循环即可解决。没有优化方式。

    #include<iostream>
    using namespace std;
    int dp[104];
    int w[104],v[104];
    int main(){
        int N,V,s;
        cin >> N >> V;
        for(int i = 1; i <= N; i++){
            cin >> s;
            for(int j = 1; j <= s; j++)
                cin >> v[j] >> w[j];
            for(int j = V; j >= 0;j--){
                //从第i组中选一个
                for(int k = 1; k <= s; k++){
                    if(j >= v[k]) dp[j] = max(dp[j],dp[j-v[k]]+w[k]);
                }
            }
        }
        cout << dp[V];
    }

    7.有依赖的背包问题 Acwing 10

    有 N个物品和一个容量是 V 的背包。

    物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。

    如下图所示:

    如果选择物品5,则必须选择物品1和2。这是因为2是5的父节点,1是2的父节点。

    每件物品的编号是 i,体积是 vi,价值是 wi,依赖的父节点编号是 pi。物品的下标范围是 1…N1…N。

    求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。

    输出最大价值。

    链式前向星还没学,邻接矩阵更方便:

    #include<iostream>
    #include<cstring>
    #include<vector>
    using namespace std;
    #define N 104
    int mp[N][N];
    int dp[N][N];
    int n,m;
    vector<int> v(N),w(N);
    void dfs(int root){
        for(int i = 1; i <=n; i++){
            //下一个子节点在不同空间下可以看成是一个组,求出这个组;
            if(mp[root][i]) dfs(i);
            else continue;
            //分组背包问题,在第i组中选一个最大的
            for(int j = m-v[root]; j >=0;j--){//root一定要选,为root留一个位置
                for(int k = 0; k <= j; k++)
                    dp[root][j] = max(dp[root][j],dp[root][j-k]+dp[i][k]);
            }
        }
        //一定要选root,选不了root的话,就直接为0;
        for(int j = m; j >= 0; j--)
            if( j >= v[root]) dp[root][j] = dp[root][j-v[root]] + w[root];
            else dp[root][j] = 0;
    }
    int main(){
        memset(mp,0,sizeof mp);
        cin >> n >> m;
        int p, root;
        //用邻接矩阵储存树,并找到根节点
        for(int i = 1;i <= n; i++){
            cin >> v[i] >> w[i] >>p;
            if( p == -1) root = i;
            else mp[p][i] = 1;
        }
        dfs(root);
        cout << dp[root][m];
    }

    8.背包问题求方案数 Acwing 11

    有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

    第 i 件物品的体积是 vi,价值是 wi。

    求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

    输出 最优选法的方案数。注意答案可能很大,请输出答案模 10^9+7 的结果。

    只需加一个数组,然后在01背包基础上修改即可:

    #include<iostream>
    #include<cstring>
    #include<vector>
    using namespace std;
    int main(){
        int n, V;
        cin >> n >> V;
        int v[n+1],w[n+1];
        for(int i = 1; i <= n; i++)
            cin >> v[i] >> w[i];
        int dp[V+1];////dp[j]表示背包容量是j的情况下的最大价值。
        vector<int> p(V+1,1);//加一个数组表示方法数
        int mod = 1e9+7;
        memset(dp,0,sizeof(dp));
        for(int i = 1;i <=n; i++)
            for(int j = V;j>= v[i];j--)
                //dp[j] = max(dp[j],dp[j-v[i]]+w[i]);变为如下:
                {
                    if(dp[j] < dp[j-v[i]]+w[i]){
                        dp[j] = dp[j-v[i]]+w[i];
                        p[j] = p[j-v[i]];
                    }
                    else if(dp[j] == dp[j-v[i]]+w[i]){
                        p[j] += p[j-v[i]];
                        p[j] %= mod;
                    }
                }
        cout <<p[V];
    }

    9.背包问题求具体方案 Acwing 12

    一维数组似乎不能解决;

    #include<iostream>
    #include<cstring>
    using namespace std;
    #define N 1004
    int main(){
        int n, V;
        cin >> n >> V;
        int v[N+1],w[N+1];
        for(int i = 1; i <= n; i++)
            cin >> v[i] >> w[i];
        int dp[N+1][N+1];////dp[i][j]表示前i个物品,背包容量是j的情况下的最大价值。
        int p[N+1][N+1];//用来记录路径
        memset(dp,0,sizeof(dp));
        memset(p,0,sizeof p);
        //用字典序排序时,要从后往前遍历
        for(int i = n; i >= 1; i--)
            for(int j = 1; j <= V; j++){
                //在没有max时要假设先不选
                dp[i][j] = dp[i+1][j];//这句容易忘,位置也要注意
                if(v[i] <= j){
                    int temp = dp[i+1][j-v[i]] + w[i];
                    if(temp >= dp[i][j]){
                        dp[i][j] = temp;
                        p[i][j] = 1;
                    }
                }
            }
        //cout << dp[1][V] << endl;
        int vol = V;
        for(int i = 1; i <= n; i++){
            if(p[i][vol] == 1){
                cout << i << ' ';
                vol -= v[i];
            }
        }
    }
  • 相关阅读:
    周总结3
    周总结6
    Java时间日期格式转换
    [专贴]在使用了母版页的内容页后,如何在javascript中调用服务器控件值
    用到函数的题目
    javascript解析dom(2)
    javascript解析dom
    自己的分页
    javascript解析DOM(3)
    转载 ajax XML dataset
  • 原文地址:https://www.cnblogs.com/Aliencxl/p/12317725.html
Copyright © 2011-2022 走看看