【算法】背包九讲

背包九讲板子

例题参考《信息学奥赛一本通》

初始化分两种情况
1、如果背包要求正好装满则初始化 f[0] = 0, f[1~v] = -INF;
2、如果不需要正好装满 f[0~v] = 0;


01背包

有N件物品和一个容量为V的背包。第i件物品的费用(即体积,下同)是w[i],价值是c[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

例题
【问题描述】
一个旅行者有一个最多能用m公斤的背包,现在有n件物品,它们的重量分别是W1,W2,...,Wn,它们的价值分别为C1,C2,...,Cn.若每种物品只有一件求旅行者能获得最大总价值。
【输入格式】
第一行:两个整数,M(背包容量,M<=200)和N(物品数量,N<=30);
第2..N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。
【输出格式】
仅一行,一个数,表示最大总价值。
【样例输入】package.in
10 4
2 1
3 3
4 5
7 9
【样例输出】package.out
12

  • 代码

解法一

#include<cstdio>
using namespace std;
const int maxn=201,maxn=31;
int m,n;
int w[maxn],c[maxn];
int f[maxn][maxm]; 
int max(int x,int y)  { x>y?x:y;}                     //求x和y最大值
int main()
{
    scanf("%d%d",&m,&n);         //背包容量m和物品数量n
    for (int i=1;i<=n;i++)         //在初始化循环变量部分,定义一个变量并初始化
    scanf("%d%d",&w[i],&c[i]);    //每个物品的重量和价值
    for(int i=1;i<=n;i++)         // f[i][v]表示前i件物品,总重量不超过v的最优价值
    for(int v=m;v>0;v--)
    if(w[i]<=v)  
    f[i][v]=max(f[i-1][v],f[i-1][v-w[i]]+c[i]);
    else
    f[i][v]=f[i-1][v];
    printf("%d",f[n][m]);           // f[n][m]为最优解
    return 0;
}

解法二

#include<cstdio>
using namespace std;
const int maxm=2001,maxn=31;
int m,n;
int w[maxn],c[maxn];
int f[maxm]; 
int main()
{
    scanf("%d%d",&m,&n);            //背包容量m和物品数量n
    for (int i=1;i<=n;i++)
    scanf("%d%d",&w[i],&c[i]);     //每个物品的重量和价值
    for(int i=1;i<=n;i++)             //设f(v)表示重量不超过v公斤的最大价值
    for(int v=m;v>=w[i];v--)
    if(f[v-w[i]]+c[i]>f[v])
    f[v]=f[v-w[i]]+c[i];
    printf("%d",f[m]);                      // f(m)为最优解
    return 0;
}

完全背包

有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是w[i],价值是c[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

  • 例题
    【问题描述】
      设有n种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为M,今从n种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于M,而价值的和为最大。
    【输入格式】
    第一行:两个整数,M(背包容量,M<=200)和N(物品数量,N<=30);
    第2..N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。
    【输出格式】
    仅一行,一个数,表示最大总价值。
    【样例输入】knapsack.in
    10 4
    2 1
    3 3
    4 5
    7 9
    【样例输出】knapsack.out
      max=12

  • 代码
    解法一

#include<cstdio>
using namespace std;
const int maxm=201,maxn=31;
int m, n;
int w[maxn],c[maxn];
int f[maxn][maxm]; 
int main()
{
    scanf("%d%d",&m,&n);            //背包容量m和物品数量n
    for(int i=1;i<=n;i++) 
    scanf("%d%d",&w[i],&c[i]);    //每个物品的重量和价值
    for(int i=1;i<=n;i++)            //f[i][v]表示前i件物品,总重量不超过v的最优价值
    for(int v=1;v<=m;v++)
    if(v < w[i])  
    f[i][v]=f[i-1][v];
    else
  if(f[i-1][v]>f[i][v-w[i]]+c[i])
    f[i][v]=f[i-1][v];
    else 
    f[i][v]=f[i][v-w[i]]+c[i]; 
    printf("max=%d",f[n][m]);         // f[n][m]为最优解
    return 0;
}

解法二

#include<cstdio>
using namespace std;
const int maxm=2001,maxn=31;
int n,m,v,i;
int c[maxn],w[maxn];
int f[maxm];
int main()
{
    scanf("%d%d",&m,&n);            //背包容量m和物品数量n
    for(i=1;i<=n;i++) 
    scanf("%d%d",&w[i],&c[i]);
    for(i=1;i<=n;i++)
    for(v=w[i];v<=m;v++)          //设 f[v]表示重量不超过v公斤的最大价值
    if(f[v-w[i]]+c[i]>f[v])
    f[v]=f[v-w[i]]+c[i];
    printf("max=%d\n",f[m]);           // f[m]为最优解
    return 0;
}


多重背包

有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是w[i],价值是c[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

  • 例题
    庆功会
    【问题描述】
    为了庆贺班级在校运动会上取得全校第一名成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动员。期望拨款金额能购买最大价值的奖品,可以补充他们的精力和体力。
    【输入格式】
    第一行二个数n(n<=500),m(m<=6000),其中n代表希望购买的奖品的种数,m表示拨款金额。
    接下来n行,每行3个数,v、w、s,分别表示第I种奖品的价格、价值(价格与价值是不同的概念)和购买的数量(买0件到s件均可),其中v<=100,w<=1000,s<=10。
    【输出格式】
    第一行:一个数,表示此次购买能获得的最大的价值(注意!不是价格)。
    【输入样例】
    5 1000
    80 20 4
    40 50 9
    30 50 7
    40 30 6
    20 20 1
    【输出样例】
    1040
  • 代码
    解法一
#include<iostream>
using namespace std;
int n,m;
int f[6002];
int v[6002],w[6002],s[6002];
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=m;j>=0;j--)
    for(int k=0;k<=s[i];k++)
    {
        if(j-k*v[i]<0)
        break;
        f[j]=max(f[j],f[j-k*v[i]]+k*w[i]);
    }
    cout<<f[m];
}

解法二(二进制优化)

#include<iostream>
using namespace std;
int n,m;
int f[6002];
int v[6002],w[6002];
int v1,w1,s;
int n1=0;
int main()
{
    cin>>n>>m;
    while(n--)//把每种划分成不同种类的 
    {
        cin>>v1>>w1>>s;
        for(int i=1;i<=s;i*=2)//二进制分解 
        //把14分成1,2,4,6多出1在后面打包 
        {
            v[++n1]=i*v1;
            w[n1]=i*w1;
            s-=i;
        }
        if(s>0)//多出来的一起打包 
        {
            v[++n1]=s*v1;
            w[n1]=s*w1;
        }
    }
    for(int i=1;i<=n1;i++)
    for(int j=m;j>=v[i];j--)
    {
        f[j]=max(f[j],f[j-v[i]]+w[i]);
    }
    cout<<f[m];
}

混合背包

将01背包、完全背包、多重背包混合起来。也就是说,有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。

  • 例题
    【问题描述】
    一个旅行者有一个最多能用V公斤的背包,现在有n件物品,它们的重量分别是W1,W2,...,Wn,它们的价值分别为C1,C2,...,Cn。有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
    【输入格式】
    第一行:二个整数,V(背包容量,V<=200),N(物品数量,N<=30);
    第2..N+1行:每行三个整数Wi,Ci,Pi,前两个整数分别表示每个物品的重量,价值,第三个整数若为0,则说明此物品可以购买无数件,若为其他数字,则为此物品可购买的最多件数(Pi)。
    【输出格式】
    仅一行,一个数,表示最大总价值。
    【样例输入】mix.in
    10 4
    2 1 0
    3 3 1
    4 5 4
    【样例输出】mix.out
    11
    【样例解释】
    选第一件物品1件和第三件物品2件。
  • 代码
#include<cstdio>
using namespace std;
int m,n;
int w[31],c[31],p[31];
int f[201];
int max(int x,int y)
{  
    return
    x>y?x:y; 
}
int main()
{
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++)
    scanf("%d%d%d",&w[i],&c[i],&p[i]);
    for(int i=1;i<=n;i++)
    if(p[i]==0)
    {                                   //完全背包
        for(int j=w[i];j<=m;j++)
        f[j]=max(f[j],f[j-w[i]]+c[i]);
    }
    else
    {
        for(int j=1;j<=p[i];j++)           //01背包和多重背包
        for(int k=m;k>=w[i];k--)
        f[k]=max(f[k],f[k-w[i]]+c[i]);
    }
    printf("%d",f[m]); 
    return 0;
}

二维费用背包

二维费用的背包问题是指:对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。设这两种代价分别为代价1和代价2,第i件物品所需的两种代价分别为a[i]和b[i]。两种代价可付出的最大值(两种背包容量)分别为V和U。物品的价值为c[i]。

  • 例题
    【问题描述】
    潜水员为了潜水要使用特殊的装备。他有一个带2种气体的气缸:一个为氧气,一个为氮气。让潜水员下潜的深度需要各种的数量的氧和氮。潜水员有一定数量的气缸。每个气缸都有重量和气体容量。潜水员为了完成他的工作需要特定数量的氧和氮。他完成工作所需气缸的总重的最低限度的是多少?
    例如:潜水员有5个气缸。每行三个数字为:氧,氮的(升)量和气缸的重量:
    3 36 120
    10 25 129
    5 50 250
    1 45 130
    4 20 119
    如果潜水员需要5升的氧和60升的氮则总重最小为249(1,2或者4,5号气缸)。
    你的任务就是计算潜水员为了完成他的工作需要的气缸的重量的最低值。
    【输入格式】
    第一行有2整数m,n(1<=m<=21,1<=n<=79)。它们表示氧,氮各自需要的量。
    第二行为整数k(1<=n<=1000)表示气缸的个数。
    此后的k行,每行包括ai,bi,ci(1<=ai<=21,1<=bi<=79,1<=ci<=800)3整数。这些各自是:第i个气缸里的氧和氮的容量及汽缸重量。
    【输出格式】
    仅一行包含一个整数,为潜水员完成工作所需的气缸的重量总和的最低值。
  • 代码
#include<cstdio>
#include<cstring>                                   //初始化memset要用到
using namespace std;
int v,u,k;
int a[1001],b[1001],c[1001];
int f[101][101];
int main()
{
    memset(f,127,sizeof(f));                    //初始化为一个很大的正整数
    f[0][0]=0;
    scanf("%d%d%d",&v,&u,&k);
    for(int i=1;i<=k;i++)
    scanf("%d%d%d",&a[i],&b[i],&c[i]);
    for(int i=1;i<=k;i++)
    for(int j=v;j>=0;j--)
    for(int l=u;l>=0;l--)
    {
       int t1=j+a[i],t2=l+b[i];
       if(t1>v)  
       t1=v;                        //若氮、氧含量超过需求,可直接用需求量代换,
       if(t2>u)  
       t2=u;                        //不影响最优解
       if(f[t1][t2]>f[j][l]+c[i])
       f[t1][t2]=f[j][l]+c[i];
    }
    printf("%d",f[v][u]);
    return 0;
}

分组背包

有N件物品和一个容量为V的背包。第i件物品的费用是w[i],价值是c[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

  • 例题
    【问题描述】
    一个旅行者有一个最多能用V公斤的背包,现在有n件物品,它们的重量分别是W1,W2,...,Wn,它们的价值分别为C1,C2,...,Cn。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
    【输入格式】
    第一行:三个整数,V(背包容量,V<=200),N(物品数量,N<=30)和T(最大组号,T<=10);
    第2..N+1行:每行三个整数Wi,Ci,P,表示每个物品的重量,价值,所属组号。
    【输出格式】
    仅一行,一个数,表示最大总价值。
    【样例输入】group.in
    10 6 3
    2 1 1
    3 3 1
    4 8 2
    6 9 2
    2 8 3
    3 9 3
    【样例输出】group.out
    20

  • 代码

#include<cstdio>
using namespace std;
int v,n,t;
int w[31],c[31];
int a[11][32],f[201];
int main()
{
    scanf("%d%d%d",&v,&n,&t);
    for(int i=1;i<=n;i++)
    {
      int p;
        scanf("%d%d%d",&w[i],&c[i],&p);
        a[p][++a[p][0]]=i;
    }
    for(int k=1;k<=t;k++)
    for(int j=v;j>=0;j--)
    for(int i=1;i<=a[k][0];i++)
    if (j >= w[a[k][i]]) 
    {
        int tmp=a[k][i];
        if(f[j]<f[j-w[tmp]]+c[tmp])
        f[j]=f[j-w[tmp]]+c[tmp]; 
    }
    printf("%d",f[v]);
    return 0;
}

有依赖的背包

这种背包问题的物品间存在某种“依赖”的关系。也就是说,i依赖于j,表示若选物品i,则必须选物品j。为了简化起见,我们先设没有某个物品既依赖于别的物品,又被别的物品所依赖;另外,没有某件物品同时依赖多件物品。
这个问题由NOIP2006金明的预算方案一题扩展而来。遵从该题的提法,将不依赖于别的物品的物品称为“主件”,依赖于某主件的物品称为“附件”。由这个问题的简化条件可知所有的物品由若干主件和依赖于每个主件的一个附件集合组成。

思路

  1. 先把每个附件与主件分成同一组
  2. 把每一组做一个01背包(一定要选主件)
  3. 进行分组背包

#include<iostream>
using namespace std;
int n,m;
int v[80],p[80],q[80];
int s[40400],t[40400];//s数组存最终的答案 t数组存过渡答案 
int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>v[i]>>p[i]>>q[i];
        p[i]=v[i]*p[i];//先算出代价 
    }
    for(int i=1;i<=m;i++)
    {
        if(q[i]==0)
        {
            for(int j=1;j<=v[i];j++)
            t[j]=0;//比v[i]小的答案修改不了赋值成0 
            for(int j=v[i];j<=n;j++)
            {
                t[j]=s[j-v[i]]+p[i];//修改比v[i]大的答案 
            }
            for(int j=1;j<=m;j++)//寻找i的附件 
            {
                if(q[j]==i)//找到i的附件 
                {
                    for(int k=n;k>=v[i]+v[j];k--)//必须要取主件所以k>=v[i]+v[j](分组背包思想) 
                    {
                        t[k]=max(t[k],t[k-v[j]]+p[j]);//01背包把每组打包
                    }
                }
                for(int k=v[i];k<=n;k++) 
                if(t[k]>s[k]) 
                s[k]=t[k];//替换有比原有答案大的新答案 
            }
        }
    }
    cout<<s[n];
}

背包问题的方案总数

对于一个给定了背包容量、物品费用、物品间相互关系(分组、依赖等)的背包问题,除了再给定每个物品的价值后求可得到的最大价值外,还可以得到装满背包或将背包装至某一指定容量的方案总数。
对于这类改变问法的问题,一般只需将状态转移方程中的max改成sum即可。例如若每件物品均是01背包中的物品,转移方程即为f[i][v]=sum{f[i-1][v],f[i-1][v-w[i]]+c[i]},初始条件f[0][0]=1。
  事实上,这样做可行的原因在于状态转移方程已经考察了所有可能的背包组成方案。

  • 例题

【问题描述】
  给你一个n种面值的货币系统,求组成面值为m的货币有多少种方案。样例:设n=3,m=10,要求输入和输出的格式如下:
【样例输入】money.in
3 10 //3种面值组成面值为10的方案
1 //面值1
2 //面值2
5 //面值5
【样例输出】money.out
  10 //有10种方案

  • 代码
#include<cstdio>
int m,n;
int a[1001];
long long f[10001];                     //注意要用long long 
int main()
{
    scanf("%d%d",&n,&m);              //n种面值的货币,组成面值为m
    for(int i=1;i<=n;i++)
    scanf("%d",&a[i]);               //输入每一种面值
    f[0]=1;
    for(int i=1;i<=n;i++)
    for(int j=m;j>=a[i];j--)         //f[j]表示面值为j的总方案数
    for(int k=1;k<=j/a[i];k++)
    f[j]+=f[j-k*a[i]];
    printf("%lld",f[m]);                 // f[m]为最优解
    return 0;
}

一波瞎总结

总的来说背包问题并不难理解
主要是题目的灵活性需要考虑到底要使用什么背包
还有 每个背包的代码基本相似
但却有个别的差别
所以要搞清楚每个变量的定义
最好是要把代码背下来考试时才不会want to go die

猜你喜欢

转载自www.cnblogs.com/BrokenString/p/9279544.html