背包问题——(结合背包九讲)

一、01背包

对物品i只有两种选择:装入或者不装入

空间优化:要从后往前递减顺序遍历,f[j]=max(f[j],f[j-w[i]]+c[i])

模板:

#include <bits/stdc++.h>
using namespace std;
const int N=2005; 
int w[N],c[N],f[N],n,m; 
int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        cin>>w[i]>>c[i];
    }
    for(int i=1;i<=m;i++){
        for(int j=n;j>=w[i];j--){
            if(f[j-w[i]]+c[i]>f[j]) f[j]=f[j-w[i]]+c[i];
        } 
    }
    cout<<f[n]<<endl;
    return 0;
}
View Code 

【NOIP2005普及组T3】采药:(完全就是01背包)

#include <bits/stdc++.h>
using namespace std;
const int N=1005; 
int w[N],c[N];
int f[N],n,m; 
int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
        cin>>w[i]>>c[i];
    for(int i=1;i<=m;i++)
        for(int j=n;j>=w[i];j--)
            if(f[j]<f[j-w[i]]+c[i])
            {
                f[j]=f[j-w[i]]+c[i];
            }
    cout<<f[n]<<endl;
    return 0;
}
View Code

二、完全背包

每件物品有无限件可用;空间优化后同01背包相似,区别就是从前往后遍历

完全背包模板:

#include <bits/stdc++.h>
using namespace std;
const int N=2005; 
int w[N],c[N],f[N],n,m; 
int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        cin>>w[i]>>c[i];
    }
    for(int i=1;i<=m;i++){
        for(int j=w[i];j<=n;j++){
            if(f[j-w[i]]+c[i]>f[j]) f[j]=f[j-w[i]]+c[i];
        } 
    }
    cout<<"max="<<f[n]<<endl;
    return 0;
}
View Code

三、多重背包

每i件物品只有mi件可用,不在是无限件了,有限制了

这里的思想其实就是通过二进制将物品拆分为很多个01背包中的物品,然后就是跟01背包一样了

拆分代码:

for(int i=1;i<=n;i++){
        int x,y,s,t=1;
        scanf("%d %d %d",&x,&y,&s);
        while(s>=t){
            w[++num]=x*t;
            c[num]=y*t;
            s-=t;
            t*=2;//按着1,2,4,,,的顺序存储 
        }
        w[++num]=x*s;
        c[num]=y*s;//剩下的 
    }
View Code

庆功会:

问题
Description
  为了庆贺班级在校运动会上取得全校第一名成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动员。期望拨款金额能购买最大价值的奖品,可以补充他们的精力和体力。
Input
  第一行二个数n(n<=500),m(m<=6000),其中n代表希望购买的奖品的种数,m表示拨款金额。
  接下来n行,每行3个数,v、w、s,分别表示第I种奖品的价格、价值(价格与价值是不同的概念)和能购买的最大数量(买0件到s件均可),其中v<=100,w<=1000,s<=10。
Output
  一行:一个数,表示此次购买能获得的最大的价值(注意!不是价格)。
Sample Input
5 1000
80 20 4
40 50 9
30 50 7
40 30 6
20 20 1
Sample Output
1040

#include <bits/stdc++.h>
using namespace std;
const int N=6005; 
int w[N],c[N],f[N],s[N],n,m,num; 
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        int x,y,s,t=1;
        scanf("%d %d %d",&x,&y,&s);
        while(s>=t){
            w[++num]=x*t;
            c[num]=y*t;
            s-=t;
            t*=2;//按着1,2,4,,,的顺序存储 
        }
        w[++num]=x*s;
        c[num]=y*s;//剩下的 
    }
    for(int i=1;i<=num;i++){
        for(int j=m;j>=w[i];j--){
            f[j]=max(f[j],f[j-w[i]]+c[i]);
        }
    }
    cout<<f[m]<<endl;
    return 0;
}
View Code

四、混合背包

就是01背包和完全背包的混合,在求最大价值的时候就根据是属于哪一种背包的情况更改遍历的顺序就可以了,如果有多重背包的情况,就拆分成01背包就可以了

Description
  一个旅行者有一个最多能装V公斤的背包,现在有n件物品,它们的重量分别是W1,W2,...,Wn,它们的价值分别为C1,C2,...,Cn。有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
Input
  第一行两个整数,M(背包容量,M <= 200),N(物品数量,N < =30);
  第2..N+1行:每行三个整数Wi,Ci,Pi,前两个整数分别表示每个物品的重量,价值,第三个整数若为0,则说明此物品可以购买无数件,若为其他数字,则为此物品可购买的最多件数(Pi)。
Output
  输出仅一行为一个数,表示最大总价值。
Sample Input
10  3
2  1  0
3  3  1
4  5  4
Sample Output
11
混合背包
常规做法
for(int i=1;i<=n;i++)
{
    int w,c,s;scanf("%d%d%d",&w,&c,&s);
    if(s==0){a[++num].w=w;a[num].c=c;a[num].s=0;}
    else{
        int t=1;
        while(s>t){s-=t;a[++num].w=w*t;a[num].c=c*t;a[num].s=1;t*=2;}
        a[++num].w=w*s;a[num].c=c*s;a[num].s=1;
     }
}//二进制优化
for(int i=1;i<=num;i++)
{
     if(!a[i].s)
         for(int j=a[i].w;j<=m;j++)
    f[j]=max(f[j],f[j-a[i].w]+a[i].c);
     else 
         for(int j=m;j>=a[i].w;j--)
    f[j]=max(f[j],f[j-a[i].w]+a[i].c);
}
二进制拆分

五、二维费用的背包

对于每件物品,具有两种不同的费用

同样的可以进行空间优化为二维数组,做法同一维一样,物品只能取一件就是01背包做法(从后往前),完全背包九从前往后,多重背包就拆分。

打包
#include <bits/stdc++.h>
using namespace std;
const int N=1005; 
const int inf=0x3f3f3f3f;
int c[N],v[N],g[N],f[N][N],n,m,V,G;  
int main()
{   
   cin>>V>>G;
   cin>>n;    
   for(int i=1;i<=n;i++)
   {   
       cin>>c[i]>>v[i]>>g[i]; 
   }
   for(int i=1;i<=n;i++) 
       for(int j=V;j>=v[i];j--)//体积   
           for(int k=G;k>=g[i];k--)//重量     
                f[j][k]=max(f[j][k],f[j-v[i]][k-g[i]]+c[i]); 
   cout<<f[V][G]<<endl;
}
View Code

潜水员问题做法有些出入,一是转换过来是求最小值,二是可以超出背包容量,且把超出的都看成满足我们的要求:

1368 -- 【DP练习】潜水员
Description
  潜水员为了潜水要使用特殊的装备。他有一个带2种气体的气缸:一个为氧气,一个为氮气。让潜水员下潜的深度需要各种的数量的氧和氮。潜水员有一定数量的气缸。每个气缸都有重量和气体容量。潜水员为了完成他的工作需要特定数量的氧和氮。他完成工作所需气缸的总重的最低限度的是多少?
  例如:潜水员有5个气缸。每行三个数字为:氧,氮的(升)量和气缸的重量:
  3 36 120
  10 25 129
  5 50 250
  1 45 130
  4 20 119
  如果潜水员需要5升的氧和60升的氮则总重最小为249(1,2或者4,5号气缸)。
  你的任务就是计算潜水员为了完成他的工作需要的气缸的重量的最低值。
Input
  第一行有2整数m,n(1<=m<=211<=n<=79)。它们表示氧,氮各自需要的量。
  第二行为整数k(1<=n<=1000)表示气缸的个数。
  此后的k行,每行包括ai,bi,ci(1<=ai<=211<=bi<=791<=ci<=800)3整数。这些各自是:第i个气缸里的氧和氮的容量及汽缸重量。
Output
  仅一行包含一个整数,为潜水员完成工作所需的气缸的重量总和的最低值。
Sample Input
5 60
5
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
Sample Output
249
潜水员
#include <bits/stdc++.h>
using namespace std;
const int N=1001; 
int w[N],o[N],f[101][101],n[N],m,sumo,sumn; 
int main()
{
    memset(f,0x3f,sizeof(f));f[0][0]=0;
    cin>>sumo>>sumn;
    cin>>m;
    for(int i=1;i<=m;i++){
        cin>>o[i]>>n[i]>>w[i]; 
    }
    for(int i=1;i<=m;i++){
        for(int j=sumo;j>=0;j--){
            for(int k=sumn;k>=0;k--){
                int t1=j+o[i],t2=k+n[i];
                if(t1>sumo) t1=sumo;
                if(t2>sumn) t2=sumn;
                if(f[t1][t2]>f[j][k]+w[i]) f[t1][t2]=f[j][k]+w[i];
            }
        }
    }
    cout<<f[sumo][sumn]<<endl;
    return 0;
}
View Code

六、分组背包问题

有些物品被分成了若干组,每组中的物品相互冲突,最多只能取一件:

该问题就变成了要么不取这一组的物品,要么就取该组的某一件。我们将状态表示为f[k][v],表示取前k组且背包容量为v时的最大价值,那么就有状态转移方程为:f[k][v]=max(f[k-1][v],f[k-1][v-w[i]]+c[i]);同样可以空间优化

伪代码为:

for(所有的组k)

 for(int j=v;j>=0;j++)

  for(属于组k的i)

    f[j]=max(f[j],f[j-w[i]]+c[i]);

1369 -- 【DP练习】分组背包
Description
  一个旅行者有一个最多能装V公斤的背包,现在有n件物品,它们的重量分别是W1,W2,...,Wn,它们的价值分别为C1,C2,...,Cn。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
Input
  第一行:三个整数,V(背包容量,V<=200),N(物品数量,N<=30)和T(最大组号,T<=10);
  第2..N+1行:每行三个整数Wi,Ci,P,表示每个物品的重量,价值,所属组号。
Output
  仅一行,一个数,表示最大总价值。
Sample Input
10 6 3
2 1 1
3 3 1
4 8 2
6 9 2
2 8 3
3 9 3
Sample Output
20
分组背包
#include <bits/stdc++.h>
using namespace std;
const int N=205; 
int w[N],c[N],f[N],a[15][N];
int v,n,t;
int main()
{
    cin>>v>>n>>t;
    for(int i=1;i<=n;i++){
        int p;
        cin>>w[i]>>c[i]>>p;
        a[p][++a[p][0]]=i;
    }
    for(int i=1;i<=t;i++)
        for(int j=v;j>=0;j--)
            for(int k=1;k<=a[i][0];k++){
                if(j>=w[a[i][k]]){
                    int tmp=a[i][k];
                    if(f[j]<f[j-w[tmp]]+c[tmp])
                        f[j]=f[j-w[tmp]]+c[tmp];
                }
            }
    cout<<f[v]<<endl;
    return 0;
}
View Code

七、背包问题的方案总数

要得到装满背包或者将背包装至某一指定容量的方案总数。

货币系统:

1370 -- 【DP练习】货币系统
Description
  给你一个n种面值的货币系统,求组成面值为m的货币有多少种方案。
Input
  第一行为n和m。
Output
  一行,方案数。
Sample Input
3 10        //3种面值组成面值为10的方案
1           //面值1
2           //面值2
5           //面值5
Sample Output
10          //有10种方案
货币系统

思路:这个问题就相当于求出将背包装满到m时有多少种方案;物品可以有无限件,相当于完全背包;这种情况下将f[0]=1;f[j]+=f[j-w[i]];如果能够减去一件物品为0就相当于多一种情况

#include <bits/stdc++.h>
using namespace std;
const int N=205; 
int a[N],n,m;
long long f[10001]; 
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    f[0]=1;
    for(int i=1;i<=n;i++)
        for(int j=a[i];j<=m;j++)
            f[j]+=f[j-a[i]];
    cout<<f[m]<<endl;
    return 0;
}
View Code

数字组合:

Description
  有n个正整数,找出其中和为t(t也是正整数)的可能的组合方式。如:
  n=5,5个数分别为1,2,3,4,5,t=5;
  那么可能的组合有5=1+4和5=2+3和5=5三种组合方式。
Input
  输入的第一行是两个正整数n和t,用空格隔开,其中1<=n<=20,表示正整数的个数,t为要求的和(1<=t<=1000);
  接下来的一行是n个正整数,用空格隔开。
Output
  和为t的不同的组合方式的数目。
Sample Input
5 5
1 2 3 4 5
Sample Output
3
数字组合

思路:跟上面那个题进行一个区别的话,这就是一个物品最多能取一件的求方案的问题了,相当于01背包问题;

#include <bits/stdc++.h>
using namespace std;
const int N=100005; 
int w[N],c[N];
int f[N],n,m; 
inline int read()
{
    int x=0;char y='*',z=getchar();
    while(z<'0'||z>'9') y=z,z=getchar();
    while(z>='0'&&z<='9') x=(x<<3)+(x<<1)+(z-'0'),z=getchar();
    return y=='-'?-x:x;
}
int main()
{
    n=read();m=read();
    for(int i=1;i<=n;i++){
        w[i]=read();
    }
    f[0]=1;
    for(int i=1;i<=n;i++)
        for(int j=m;j>=w[i];j--)
            f[j]+=f[j-w[i]];
    printf("%d\n",f[m]);
    return 0;
}
View Code

再来就是很多的硬币问题了,求最小需要的数:

1515 -- 【DP练习】无限硬币问题
Description
输入硬币的n种不同面值(各种面值的硬币个数不限)和m,输出构成1到m元的最少硬币数。
Input
第一行两个数n(n<=200),m(m<=10000)
第二行n个面值
Output
共m行,每行为组成该值所需最少硬币数(若不能组成则输出-1)
Sample Input
3 4
1 2 3
Sample Output
1
1
1
2
无限硬币问题
#include <bits/stdc++.h>
using namespace std;
const int N=1005;
const int inf=0x3f3f3f3f;
int n,m,f[N],a[N];
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=m;i++) f[i]=inf;
    f[0]=0;
    for(int i=1;i<=n;i++){
        for(int j=a[i];j<=m;j++){//无限个从前往后
            f[j]=min(f[j],f[j-a[i]]+1);
        }
    }
    for(int i=1;i<=m;i++) cout<<f[i]<<endl;
    return 0;
}
View Code
#include <bits/stdc++.h>
using namespace std;
const int N=1005;
const int inf=0x3f3f3f3f;
int n,m,f[N],a[N];
int ans[N];
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=m;i++) f[i]=inf;
    f[0]=0;
    for(int i=1;i<=n;i++){
        for(int j=a[i];j<=m;j++){
            if(f[j]>f[j-a[i]]+1){
                ans[j]=a[i];
                f[j]=f[j-a[i]]+1;
            }
        }
    }
    int s;
    cin>>s;
    while(s){
        cout<<ans[s]<<" ";
        s-=ans[s];
    }
    return 0;
}
如需打印出来
1517 -- 【DP练习】有限硬币问题
Description
输入硬币的n种不同面值(指定各种面值的硬币个数)和m,输出构成1到m元的最少硬币数。
Input
第一行n,m(1<=n<=100,1<=m<=10000)
第二行n个数表示硬币的面值
第三行n个数表示每种硬币的数量
Output
m行,每行表示取得该值的最小硬币个数,若不能则输出-1
Sample Input
2 4
1 2
1 1
Sample Output
1
1
2
-1
有限硬币问题
#include <bits/stdc++.h>
using namespace std;
const int N=10005; 
const int inf=0x3f3f3f3f;
int a[N],f[N],g[N],n,m;  
int main(){
    cin>>n>>m;
    memset(f,0x3f,sizeof(f));
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        //f[a[i]]=1;
    }
    for(int i=1;i<=n;i++)
    {
        cin>>g[i];
    }
    f[0]=0;
    for(int i=1;i<=n;i++)
        for(int k=1;k<=g[i];k++)
            for(int j=m;j>=a[i];j--)
            {
                f[j]=min(f[j],f[j-a[i]]+1);
            }        
    for(int i=1;i<=m;i++)
    {
        if(f[i]<inf) cout<<f[i]<<endl;
        else cout<<-1<<endl; 
    }
    return 0;
}
View Code

有一种稍微要难一点的,就是求出背包能够获得最大价值的的方案数;

首先就是求出最大价值是多少,然后就是求出背包获得该最大价值的方案数:

有N件物品和一个容量是V的背包。每件物品只能使用一次。
第i件物品的体积是vi,价值是wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 最优选法的方案数。注意答案可能很大,请输出答案模10^9+7 的结果。
输入格式
第一行两个整数N,V用空格隔开,分别表示物品数量和背包容积。
接下来有N行,每行两个整数vi,wi用空格隔开,分别表示第i件物品的体积和价值。
输出格式
输出一个整数,表示 方案数 模 10^9+7的结果。
数据范围
0<N,V≤10000<N,V≤1000
0<vi,wi≤10000<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 6
输出样例:
2
背包问题求方案数

在这里要注意一个问题,就是我们的f数组的初始化细节,在求背包的最优解的时候会有两种问法:

1、恰好装满,这种情况除开将f[0]=0以外,其他的初始化为-inf;相当于其他都是不合法的;

2、没有要求必须装多少时,只是求价格尽量大,初始化时就可以全部定义为0;

#include<bits/stdc++.h>
using namespace std;
const int N=1005;
const int mod=1e9+7;
const int inf=100000;
int f[N],g[N],v[N],c[N];
int main()
{
    int n,m;cin>>n>>m;
    for(int i=1;i<=m;i++) f[i]=-inf;
    g[0]=1;
    for(int i=1;i<=n;i++){
        cin>>v[i]>>c[i];
        for(int j=m;j>=v[i];j--)
        {
            int t=max(f[j],f[j-v[i]]+c[i]);
            int s=0;
            if(t==f[j]) s+=g[j];
            if(t==f[j-v[i]]+c[i]) s+=g[j-v[i]];
            f[j]=t;
            g[j]=s%mod;
        }
    }
    int maxx=0,ans=0; 
    for(int i=0;i<=m;i++) maxx=max(maxx,f[i]);
    for(int i=0;i<=m;i++) 
        if(f[i]==maxx) 
            ans+=g[i];
    cout<<ans%mod<<endl;
    
}
View Code

八、有依赖的背包问题

背包间的物品存在着依赖关系,例如有主件和附件之分,要买附件就一定得买主件,这种题其实我们可以把他拆分成分组背包问题。

例如:主件1,它的附件为2,3;我们就发现只有这几种策略:(1)、(1,2)、(1,3)、(1,2,3);这四种策略就相当于分组背包,因为我们只能取其中一种,这就是我们做这种题的一个思想:

金明的预算方案:

1514 -- 【DP练习】金明的预算方案(NOIP2006提高)
Description
  金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”。今天一早,金明就开始做预算了,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
  
  如果要买归类为附件的物品,必须先买该附件所属的主件。每个主件可以有0个、1个或2个附件。附件不再有从属于自己的附件。金明想买的东西很多,肯定会超过妈妈限定的N元。于是,他把每件物品规定了一个重要度,分为5等:用整数1~5表示,第5等最重要。他还从因特网上查到了每件物品的价格(都是10元的整数倍)。他希望在不超过N元(可以等于N元)的前提下,使每件物品的价格与重要度的乘积的总和最大。
  设第j件物品的价格为v[j],重要度为w[j],共选中了k件物品,编号依次为j1,j2,……,jk,则所求的总和为:v[j1]*w[j1]+v[j2]*w[j2]+ …+v[jk]*w[jk]。(其中*为乘号)请你帮助金明设计一个满足要求的购物单。
Input
  输入文件的第1行,为两个正整数,用一个空格隔开:
  N m 
  其中N(<32000)表示总钱数,m(<60)为希望购买物品的个数。)
  从第2行到第m+1行,第j行给出了编号为j-1的物品的基本数据,每行有3个非负整数
  v p q
  (其中v表示该物品的价格(v<10000),p表示该物品的重要度(1~5),q表示该物品是主件还是附件。如果q=0,表示该物品为主件,如果q>0,表示该物品为附件,q是所属主件的编号)
Output
  输出文件只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<200000)。 
Sample Input
1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0
Sample Output
2200
金明的预算方案

做法我自己把它分为三步:

第一步:存入信息(注意附件的存储需要注明主件)

scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d %d %d",&a[i].v,&a[i].p,&a[i].q);
        a[i].p*=a[i].v;
        if(a[i].q){
            num[a[i].q]++;
            fu[a[i].q][num[a[i].q]].v=a[i].v;//用fu[][]这个结构体数组就记录了附件的信息;
            fu[a[i].q][num[a[i].q]].p=a[i].p;
            fu[a[i].q][num[a[i].q]].q=a[i].q;
        }
    }

第二步(难点):进行分组

for(int i=1;i<=m;i++)
    {
        if(num[i])//存在附件
        {
            memset(f,-1,sizeof(f));//-1表示没有恰好到这个价钱
            f[0]=0;
            for(int j=1;j<=num[i];j++)
                for(int k=n-a[i].v;k>=fu[i][j].v;k--)
                    if(f[k-fu[i][j].v]!=-1)
                        f[k]=max(f[k],f[k-fu[i][j].v]+fu[i][j].p);//求出附件中恰好能出的价格能得到的价值
            for(int j=0;j<=n-a[i].v;j++)
            {
                if(f[j]!=-1)//附件中能恰好付出价格j得到的价值 
                {
                    v[i][0]++;
                    v[i][v[i][0]]=j+a[i].v;
                    p[i][v[i][0]]=f[j]+a[i].p;
                }//完成的对主件i的分组 
             } 
        }
        else if(!a[i].q)
        {
            v[i][0]++;
            v[i][v[i][0]]=a[i].v;
            p[i][v[i][0]]=a[i].p;
        }
    }

 第三步:就是按照分组背包来操作,一共有n组

memset(f,0,sizeof(f));
    for(int i=1;i<=m;i++)
        for(int j=n;j>=0;j--)
            for(int k=1;k<=v[i][0];k++)
            {
                if(j-v[i][k]>=0)
                    f[j]=max(f[j],f[j-v[i][k]]+p[i][k]);
             } 
#include<bits/stdc++.h>
using namespace std;
const int N=32005;
int n,m;
int f[N],h[N];
struct node
{
    int v,p,q;
}a[65],fu[65][5];
int num[65];
int v[65][5],p[65][5];
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d %d %d",&a[i].v,&a[i].p,&a[i].q);
        a[i].p*=a[i].v;
        if(a[i].q){
            num[a[i].q]++;
            fu[a[i].q][num[a[i].q]].v=a[i].v;
            fu[a[i].q][num[a[i].q]].p=a[i].p;
            fu[a[i].q][num[a[i].q]].q=a[i].q;
        }
    }
    for(int i=1;i<=m;i++)
    {
        if(num[i])//存在附件
        {
            memset(f,-1,sizeof(f));//-1表示没有恰好到这个价钱
            f[0]=0;
            for(int j=1;j<=num[i];j++)
                for(int k=n-a[i].v;k>=fu[i][j].v;k--)
                    if(f[k-fu[i][j].v]!=-1)
                        f[k]=max(f[k],f[k-fu[i][j].v]+fu[i][j].p); //求出附件中恰好能出的价格能得到的价值
            for(int j=0;j<=n-a[i].v;j++)
            {
                if(f[j]!=-1)//附件中能恰好付出价格j得到的价值 
                {
                    //cout<<j<<":"<<f[j]<<endl;
                    v[i][0]++;
                    v[i][v[i][0]]=j+a[i].v;
                    p[i][v[i][0]]=f[j]+a[i].p;
                }//完成的对主件i的分组 
             } 
        }
        else if(!a[i].q)
        {
            v[i][0]++;
            v[i][v[i][0]]=a[i].v;
            p[i][v[i][0]]=a[i].p;
        }
    }
    memset(f,0,sizeof(f));
    for(int i=1;i<=m;i++)
        for(int j=n;j>=0;j--)
            for(int k=1;k<=v[i][0];k++)
            {
                if(j-v[i][k]>=0)
                    f[j]=max(f[j],f[j-v[i][k]]+p[i][k]);
             } 
    int ans=0;
    for(int i=0;i<=n;i++)
        ans=max(ans,f[i]);
    printf("%d\n",ans);
    return 0;
}
完整代码

 练习题目:

1、宠物小精灵之收服(二维费用的背包问题,但是需要剩余的体力最多,所以要从前往后找到最小的f[n][i]的最小值)

【题目描述】
宠物小精灵是一部讲述小智和他的搭档皮卡丘一起冒险的故事。

一天,小智和皮卡丘来到了小精灵狩猎场,里面有很多珍贵的野生宠物小精灵。小智也想收服其中的一些小精灵。然而,野生的小精灵并不那么容易被收服。对于每一个野生小精灵而言,小智可能需要使用很多个精灵球才能收服它,而在收服过程中,野生小精灵也会对皮卡丘造成一定的伤害(从而减少皮卡丘的体力)。当皮卡丘的体力小于等于0时,小智就必须结束狩猎(因为他需要给皮卡丘疗伤),而使得皮卡丘体力小于等于0的野生小精灵也不会被小智收服。当小智的精灵球用完时,狩猎也宣告结束。

我们假设小智遇到野生小精灵时有两个选择:收服它,或者离开它。如果小智选择了收服,那么一定会扔出能够收服该小精灵的精灵球,而皮卡丘也一定会受到相应的伤害;如果选择离开它,那么小智不会损失精灵球,皮卡丘也不会损失体力。

小智的目标有两个:主要目标是收服尽可能多的野生小精灵;如果可以收服的小精灵数量一样,小智希望皮卡丘受到的伤害越小(剩余体力越大),因为他们还要继续冒险。

现在已知小智的精灵球数量和皮卡丘的初始体力,已知每一个小精灵需要的用于收服的精灵球数目和它在被收服过程中会对皮卡丘造成的伤害数目。请问,小智该如何选择收服哪些小精灵以达到他的目标呢?

【输入】
输入数据的第一行包含三个整数:N(0<N<1000),M(0<M<500),K(0<K<100),分别代表小智的精灵球数量、皮卡丘初始的体力值、野生小精灵的数量。

之后的K行,每一行代表一个野生小精灵,包括两个整数:收服该小精灵需要的精灵球的数量,以及收服过程中对皮卡丘造成的伤害。

【输出】
输出为一行,包含两个整数:C,R,分别表示最多收服C个小精灵,以及收服C个小精灵时皮卡丘的剩余体力值最多为R。

【输入样例】
10 100 5
7 10
2 40
2 50
1 20
4 20
【输出样例】
3 30
【提示】
样例输入2:

10 100 5

8 110

12 10

20 10

5 200

1 110

样例输出2:

0 100

提示:

对于样例输入1:小智选择:(7,10) (2,40) (1,20) 这样小智一共收服了3个小精灵,皮卡丘受到了70点伤害,剩余100-70=30点体力。所以输出3 30。

对于样例输入2:小智一个小精灵都没法收服,皮卡丘也不会收到任何伤害,所以输出0 100
宠物小精灵之收服
#include <bits/stdc++.h>
using namespace std;
const int N=1005; 
int w[N],c[N];
int f[N][N],n,m,t; 
inline int read()
{
    int x=0;char y='*',z=getchar();
    while(z<'0'||z>'9') y=z,z=getchar();
    while(z>='0'&&z<='9') x=(x<<3)+(x<<1)+(z-'0'),z=getchar();
    return y=='-'?-x:x;
}
int main()
{
    n=read();m=read(),t=read();
    for(int i=1;i<=t;i++){
        w[i]=read();c[i]=read();
    }
    for(int i=1;i<=t;i++)
        for(int j=n;j>=w[i];j--)
            for(int k=m;k>=c[i];k--)
                f[j][k]=max(f[j][k],f[j-w[i]][k-c[i]]+1);
    for(int i=0;i<=m;i++){
        if(f[n][i]==f[n][m])
        {
            cout<<f[n][m]<<" "<<m-i<<endl;
            break;
        }
    }
    return 0;
}
View Code

2、买书(就是完全背包的方案数)

【题目描述】
小明手里有nn元钱全部用来买书,书的价格为1010元,2020元,5050元,100100元。

问小明有多少种买书方案?(每种书可购买多本)

【输入】
一个整数 nn,代表总共钱数。(0≤n≤10000≤n≤1000)

【输出】
一个整数,代表选择方案种数。

【输入样例】
20
【输出样例】
2
【提示】
样例输入

样例输入2:

15
样例输入3:

0
样例输出

样例输出2:

0
样例输出3:

0
买书
#include <bits/stdc++.h>
using namespace std;
const int N=1005; 
int w[N],c[N];
int f[N],n,m,t; 
inline int read()
{
    int x=0;char y='*',z=getchar();
    while(z<'0'||z>'9') y=z,z=getchar();
    while(z>='0'&&z<='9') x=(x<<3)+(x<<1)+(z-'0'),z=getchar();
    return y=='-'?-x:x;
}
int main()
{
    n=read();
    if(n==0) {
        cout<<0<<endl;return 0;
    }
    w[1]=10;w[2]=20;w[3]=50;w[4]=100;
    f[0]=1;
    for(int i=1;i<=4;i++)
        for(int j=w[i];j<=n;j++)
            f[j]+=f[j-w[i]];
    cout<<f[n]<<endl;
    return 0;
}
View Code

3、装箱问题、Charm Bracelet(01背包)

4、开餐馆(就是限制条件稍微改变一下)

Description
  信息学院的同学小明毕业之后打算创业开餐馆,现在共有n个地点可供选择,小明打算从中选择合适的位置开设一些餐馆,这 n个地点排列在同一条直线上,我们用一个整数序列m1,m2,...mn来表示他们的相对位置,由于地段关系,开餐馆的利润会有所不同,我们用pi 表示在mi处开餐馆的利润,为了避免自己的餐馆的内部竞争,餐馆之间的距离必须大于k,请你帮助小明选择一个总利润最大的方案。
Input
  输入第一行是整数T(1 <= T <= 1000) ,表明有T组测试数据。紧接着有T组连续的测试。每组测试数据有3行,
  第1行:地点总数n( n < 100), 距离限制k(k > 0 && k < 1000)。
  第2行:n 个地点的位置m1 , m2, ... mn(1000000 > mi > 0 且为整数,升序排列)。
  第3行:n 个地点的餐馆利润p1,p2,...pn(1000 > pi > 0 且为整数)。
Output
  对于每组测试数据可能的最大利润。
Sample Input
2
3 11
1 2 15
10 2 30
3 16
1 2 15
10 2 30
Sample Output
40
30
开餐馆
#include <bits/stdc++.h>
using namespace std;
const int N=13000,inf=0x3f3f3f;; 
int w[N],c[N];
int f[N],n,m,t; 
inline int read()
{
    int x=0;char y='*',z=getchar();
    while(z<'0'||z>'9') y=z,z=getchar();
    while(z>='0'&&z<='9') x=(x<<3)+(x<<1)+(z-'0'),z=getchar();
    return y=='-'?-x:x;
}
int main()
{
    t=read();
    while(t--){
        memset(f,0,sizeof(f));
        memset(w,0,sizeof(w));
        memset(c,0,sizeof(c));
        n=read();m=read();
        for(int i=1;i<=n;i++)    w[i]=read();
        for(int i=1;i<=n;i++)    c[i]=read();
        w[0]=-inf;
        for(int i=1;i<=n;i++)
        {
            f[i]=f[i-1];
            for(int j=0;w[j]+m<w[i];j++)//后面的餐厅 
                f[i]=max(f[i],f[j]+c[i]);
        }
        cout<<f[n]<<endl;
    }
    
    return 0;
}
View Code

 

posted @ 2020-04-02 12:10  sumoier  阅读(316)  评论(0编辑  收藏  举报