码迷,mamicode.com
首页 > 其他好文 > 详细

背包九讲

时间:2020-02-02 23:39:27      阅读:76      评论:0      收藏:0      [点我收藏+]

标签:pre   str   lis   namespace   条件   计算   print   turn   个数   

第一讲 01背包

01背包是每种武平只能选择一次,计算出最大价值的问题,先上01背包的状态转移方程:

\[ f[i][v]=max\{f[i-1][v],f[i-1][v-c[i]]+w[i]\} \]

下面来解释一下这个状态转移方程:

这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的。所以有必要将它详细解释一下:“将前i件物品放入容量为v的背包中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只牵扯前i-1件物品的问题。如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为v的背包中”,价值为f[i-1][v];如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为v-c[i]的背包中”,此时能获得的最大价值就是f[i-1][v-c[i]]再加上通过放入第i件物品获得的价值w[i]。即代码为:

//f[i][j]的意义是表示只看前i个物品,总体积是j的情况下,总价值最大是多少
for(int i=1; i<=n; ++i){
    for(int j=0; j<=C; ++j){
        if(j>=v[i]) 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][C]<<endl;

这个方程还可以对空间进行优化,下面是一位数组实现01背包:

//f[i]的意义为当前体积为i的情况下背包的最大价值 ·
for(int i=1;i<=n;++i)
        for(int j=C;j>=v[i];--j)
            dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
cout<<dp[C]<<endl;

这里是背包容积为c的最大价值,将f数组全初始化为0,如果想要求解背包体积恰好为c的情况下其最大价值是多少,只需将f[0]初始化为0,而将其他的初始化为-inf即可。

例题:

#include<bits/stdc++.h>

using namespace std;
const int N=1e3+3;
int dp[N][N],w[N],v[N];

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n,c;
        memset(dp,0,sizeof(dp));
        cin>>n>>c;
        for(int i=1; i<=n; ++i)
            cin>>w[i];
        for(int i=1; i<=n; ++i)
            cin>>v[i];
        for(int i=1; i<=n; ++i)
            for(int j=0; j<=c; ++j)
            {
                if(j>=v[i])
                    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][c]<<endl;
    }
    return 0;
}

第二讲 完全背包

完全背包是指背包里面的物品可以选择无限次或每个物品有无限个,求最大价值。下面是完全背包的状态转移方程:

\[dp[i][j]=max(dp[i-1][j],dp[i-1][j-k*v[i]]+k*w[i])\]

上面的状态转移方程可以理解为试探性取,即对于个数多与1的物品,我们可以试探性的取一次,取两次...不过这种算法的时间复杂度会比较高,其时间复杂度为O(n*V*sum(k))。下面是代码模板

for(int i=1;i<=n;++i)
    for(int j=1;j<=c;++j)
        for(int k=0;k<=c/v[i];++k)
            if(j>k*v[i])    dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
            else dp[i][j]=dp[i-1][j];

基于上面的代码,我们还可以做一些简单的优化,1.体积大于c的不要;2.同体积的取价值最大的(其余舍弃)。即使这么优化后,一般情况下还是会超时,下面是将完全背包转化为01背包来做,即状态转移方程为:

\[dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+w[i])\]

上面状态转移方程的意思为在第i件物品上,如果不取该物品,则dp[i][j]=dp[i-1][j],如果去第i件物品,则最少需要dp[i][j-v[i]]+w[i]。下面是代码模板:

//f[i]表示当前体积为i的情况下,背包的最大价值是多少
for(int i=1;i<=n:++i)
    for(int j=v[i];j<=c;++j)        //这里与01背包不同的地方是01背包是逆序枚举,完全背包是正序枚举
        dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
cout<<dp[c]<<endl;

完全背包例题模板,题目同01背包,条件加了每件物品可重复选取:

#include<bits/stdc++.h>

using namespace std;
const int N=1e3+3;
int dp[N],v[N],w[N];

int main()
{
    int n,c;
    scanf("%d%d",&n,&c);
    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<=c;++j)
            dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
    printf("%d\n",dp[c]);
    return 0;
}

第三讲 多重背包

多重背包类似于完全背包和01背包的结合版,即每个物品有有限个或则能取有限次,求最大价值的问题。

这里最简单的解法是直接将多重背包问题看做01背包问题,在对空间和物品价值遍历的中间在对个数遍历一遍就行了,其模板为:

//f[i]表示总体积为i的情况下,其背包的最大价值是多少。
for(int i=1;i<=n;++i){
        for(int j=c;j>=v[i];--j){
            for(int k=1;k<=num[i]&&k*v[i]<=j;++k)
                f[j]=max(f[j],f[j-k*v[i]]+k*w[i]);
        }
    }
cout<<dp[c]<<endl;
//类似于在01背包的情况下,再加一个for循环求个数,也是逆向枚举。

但是一般情况下上面解法会超时,上面的解法还可以进行优化,其可以通过二进制优化和单调队列优化。

二进制优化:将多个物品分成一个个二进制,从而将问题转化为01背包问题,复杂度是\(m*n*log_2(num)\);下面是模板的代码:

memset(f,0,sizeof(f));
for(int i=1;i<=n;++i){
    cin>>a>>b>>num;
    for(int k=1;k<=num;++k){
        v[cnt]=a*k;
        w[cnt++]=b*k;
        num-=k;
    }
    if(num){
        v[cnt]=a*num;
        w[cnt++]=b*num;
    }
}
for(int i=1;i<cnt;++i){
    for(int j=c;j>=v[i];--j){
        f[j]=max(f[j],f[j-v[i]]+w[i]);
    }
}
cout<<f[c]<<endl;
cnt=1;

单调队列优化:思路如下:

技术图片

show code:

第四讲 混合背包

混合背包即多个背包都可以放(01背包,多重背包,完全背包),首先将多重背包转化为01背包,之后再分类求背包最大值即可;下面是模板AC代码:

#include<bits/stdc++.h>

using namespace std;
const int maxn=1e3+10;
int f[maxn];
struct node
{
    int w,v;
    int kind;   //类别
}arr[maxn];

int main()
{
    ios::sync_with_stdio(false);
    memset(f,0,sizeof(f));
    int n,c;
    int a,b,num;
    int cnt=1;
    cin>>n>>c;
    for(int i=1;i<=n;++i){
        cin>>a>>b>>num;
        if(num==-1){
            arr[cnt].v=a;
            arr[cnt].w=b;
            arr[cnt++].kind=-1;
        }
        else if(num==0){
            arr[cnt].v=a;
            arr[cnt].w=b;
            arr[cnt++].kind=0;
        }
        else{
            for(int k=1;k<=num;k*=2){       //二进制优化,转化成01背包
                num-=k;
                arr[cnt].v=k*a;
                arr[cnt].w=k*b;
                arr[cnt++].kind=-1;
            }
            if(num){
                arr[cnt].v=num*a;
                arr[cnt].w=num*b;
                arr[cnt++].kind=-1;
            }
        }
    }
    for(int i=1;i<cnt;++i){
        if(arr[i].kind==-1){
            for(int j=c;j>=arr[i].v;j--)
                f[j]=max(f[j],f[j-arr[i].v]+arr[i].w);
        }
        else{
            for(int j=arr[i].v;j<=c;j++)
                f[j]=max(f[j],f[j-arr[i].v]+arr[i].w);
        }
    }
    cout<<f[c]<<endl;
    system("pause");
    return 0;
}

第五讲 二维费用的背包问题

顾名思义,二维费用即有两个约束条件,一个为重量,一个为背包容量,保证物品总容积既不超过背包容积,物品总重量也不超过背包承受重量。

以二维费用的01背包为例(安全背包即从前向后枚举,多重背包类似),直接枚举个数、体积、重量即可,下面是模板AC代码:

#include<bits/stdc++.h>

using namespace std;
const int maxn=2e3+5;
int v[maxn],w[maxn],s[maxn],f[maxn][maxn];
int n,vol,c;

int main()
{
    ios::sync_with_stdio(false);
    memset(f,0,sizeof(f));
    cin>>n>>c>>vol;                //个数,容量,承受最大重量
    for(int i=1;i<=n;++i)
        cin>>v[i]>>s[i]>>w[i];  //体积、重量、价值
    for(int i=1;i<=n;++i)
        for(int j=c;j>=v[i];j--)
            for(int k=vol;k>=s[i];k--)
                f[j][k]=max(f[j][k],f[j-v[i]][k-s[i]]+w[i]);
    cout<<f[c][vol]<<endl;
    system("pause");
    return 0;
}

第六讲 分组背包

分组背包既给定一个一定容积的背包,在给定若干组物品,每组有若干个物品,但是每组的物品只能选一个,求最大价值。

模板AC代码:

//f[i]是我意义是在体积为i的情况下最大收益
memset(f,0,sizeof(f));
for(int i=1;i<=num;++i)     //组的个数
{
    int n;
    cin>>n;
    for(int j=1;j<=n;++j)
        cin>>v[j]>>w[j];
    for(int j=c;j>=0;--j){      //注意这里要先遍历容积在遍历数量,这样可以保证每组选一个物品
        for(int k=1;k<=n;++k){
            if(j>=v[k]) f[j]=max(f[j],f[j-v[k]]+w[k]);
        }
    }
}
cout<<f[c]<<endl;

第七讲 有依赖的背包问题

类似于拓扑排序,所选物品有依赖关系,选这个物品必须选这个物品的父节点,求一定体积的背包的最大收益。

#include <bits/stdc++.h>

using namespace std;
const int maxn=1e3+10;
int head[maxn];
struct Edge{
    int nex,to,val;
}edge[maxn<<1];
int n,m,tot;
int v[maxn],w[maxn];        //体积,价值
int dp[maxn][maxn];         //dp[i][j]表示选节点i,总体积为j的最大价值是多少
inline void add(int from,int to){
    edge[++tot].to=to;
    edge[tot].nex=head[from];
    head[from]=tot;
}
int root,p;
void dfs(int x)
{
    for(int i=head[x];i!=-1;i=edge[i].nex)  //先循环物品组,再循环体积,再循环决策
    {
        int y=edge[i].to;
        dfs(y); 
        for(int j=m-v[x];j>=0;j--){         //求出不选x的
            for(int k=0;k<=j;++k){
                dp[x][j]=max(dp[x][j],dp[x][j-k]+dp[y][k]);
            }
        }
    }
    for(int i=m;i>=v[x];--i)    dp[x][i]=dp[x][i-v[x]]+w[x];
    for(int i=0;i<v[x];++i)     dp[x][i]=0;   //父节点选不上,一切都白选
}

int main()
{
    memset(head,-1,sizeof(head));
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;++i){
        scanf("%d %d %d",&v[i],&w[i],&p);
        if(p==-1)   root=i;
        else    add(p,i);
    }
    dfs(root);
    printf("%d\n",dp[root][m]);
    system("pause");
}

第八讲 背包问题求方案数

题目背景和01背包一样,求问题的最优解法有多少种。

AC模板:

背包九讲

标签:pre   str   lis   namespace   条件   计算   print   turn   个数   

原文地址:https://www.cnblogs.com/StungYep/p/12254086.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!