背包问题合集

AcWing 423. 采药

题干

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。
为此,他想拜附近最有威望的医师为师。
医师为了判断他的资质,给他出了一个难题。
医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是辰辰,你能完成这个任务吗?
输入格式
输入文件的第一行有两个整数T和M,用一个空格隔开,T代表总共能够用来采药的时间,M代表山洞里的草药的数目。
接下来的M行每行包括两个在1到100之间(包括1和100)的整数,分别表示采摘某株草药的时间和这株草药的价值。

输出格式
输出文件包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。

数据范围
1≤T≤1000
1≤M≤100
输入样例:
70 3
71 100
69 1
1 2
输出样例:
3

思路

总结下题意,就是背包问题,时间代表背包的大小,求最大价值,用一维背包就可以表示了。
f[i]表示经过了i时间所能采到的最大价值。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;

int f[1100];
int main(){
    int time,m,t,w;
    scanf("%d%d",&time,&m);
    for(int i=0;i<m;i++){
        scanf("%d%d",&t,&w);
        for(int j=time;j>=t;j--){
        //从最大时间开始遍历,如果想采当前药至少需要t时间
            f[j]=max(f[j-t]+w,f[j]);
        }
    }
    printf("%d\n",f[time]);
    return 0;
}

AcWing 1024. 装箱问题

题干

有一个箱子容量为 V,同时有 n 个物品,每个物品有一个体积(正整数)。
要求 n 个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。
输入格式
第一行是一个整数 V,表示箱子容量。
第二行是一个整数 n,表示物品数。
接下来 n 行,每行一个正整数(不超过10000),分别表示这 n 个物品的各自体积。
输出格式
一个整数,表示箱子剩余空间。

数据范围
0<V≤20000
0<n≤30
输入样例:
24
6
8
3
12
7
9
7
输出样例:
0

思路

总结下题意,还是标准的背包问题,剩余空间最小也就是求箱子的容量和最大。
f[i]表示容量为i的箱子所使用的最大容量和

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
using namespace std;
int f[30000];
int main(){
    int v,n,x;
    scanf("%d%d",&v,&n);
    for(int i=0;i<n;i++){
        scanf("%d",&x);
        for(int j=v;j>=x;j--){
        //依旧是从最大值开始,遍历到临界
            f[j]=max(f[j],f[j-x]+x);
        }
    }
    printf("%d\n",v-f[v]);
    return 0;
}

AcWing 1022. 宠物小精灵之收服

题干

宠物小精灵是一部讲述小智和他的搭档皮卡丘一起冒险的故事。
一天,小智和皮卡丘来到了小精灵狩猎场,里面有很多珍贵的野生宠物小精灵。
小智也想收服其中的一些小精灵。
然而,野生的小精灵并不那么容易被收服。
对于每一个野生小精灵而言,小智可能需要使用很多个精灵球才能收服它,而在收服过程中,野生小精灵也会对皮卡丘造成一定的伤害(从而减少皮卡丘的体力)。
当皮卡丘的体力小于等于0时,小智就必须结束狩猎(因为他需要给皮卡丘疗伤),而使得皮卡丘体力小于等于0的野生小精灵也不会被小智收服。
当小智的精灵球用完时,狩猎也宣告结束。
我们假设小智遇到野生小精灵时有两个选择:收服它,或者离开它。
如果小智选择了收服,那么一定会扔出能够收服该小精灵的精灵球,而皮卡丘也一定会受到相应的伤害;如果选择离开它,那么小智不会损失精灵球,皮卡丘也不会损失体力。
小智的目标有两个:主要目标是收服尽可能多的野生小精灵;如果可以收服的小精灵数量一样,小智希望皮卡丘受到的伤害越小(剩余体力越大),因为他们还要继续冒险。
现在已知小智的精灵球数量和皮卡丘的初始体力,已知每一个小精灵需要的用于收服的精灵球数目和它在被收服过程中会对皮卡丘造成的伤害数目。
请问,小智该如何选择收服哪些小精灵以达到他的目标呢?
输入格式
输入数据的第一行包含三个整数:N,M,K,分别代表小智的精灵球数量、皮卡丘初始的体力值、野生小精灵的数量。
之后的K行,每一行代表一个野生小精灵,包括两个整数:收服该小精灵需要的精灵球的数量,以及收服过程中对皮卡丘造成的伤害。
输出格式
输出为一行,包含两个整数:C,R,分别表示最多收服C个小精灵,以及收服C个小精灵时皮卡丘的剩余体力值最多为R。

数据范围
0<N≤1000,
0<M≤500,
0<K≤100
输入样例1:
10 100 5
7 10
2 40
2 50
1 20
4 20
输出样例1:
3 30
输入样例2:
10 100 5
8 110
12 10
20 10
5 200
1 110
输出样例2:
0 100

思路

总结下很长的题意,就是你有n个球,m点生命值,k个宝可梦,抓当前宝可梦需要消耗xi个球、yi点生命值,求最多抓取数,相同抓取数时最大的剩余生命值。
①因为有两种限制,所以需要用二维背包,f[i][j]表示当前有i个球,使用的生命值为j(即攻击力)最多可以抓几个宝可梦。这样f[n][m]就可以表示最大抓取数。
然后考虑最大剩余生命值,因为j表示的是使用过的生命值(即怪物的攻击力)所以我们要使j尽可能的小,这样就是找f[n][j]==f[n][m]的最小j。

//第一种
//yxc大佬的做法
//f[i][j]表示消耗了i个球,j点攻击力情况下的最大捕捉数。
//然后考虑最少生命消耗,遍历f[n][j],寻找与f[n][m]相同的捕捉数, j从m-1(因为生命值不能为0)递减(因为要求最大剩余生命值)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int x[1100],y[510];
int f[1100][510];
int main(){
    int n,m,k;
    scanf("%d%d%d",&n,&m,&k);
    for(int i=0;i<k;i++){
        scanf("%d%d",&x[i],&y[i]);
        for(int j=n;j>=x[i];j--){
            for(int l=m-1;l>=y[i];l--){
                f[j][l]=max(f[j][l],f[j-x[i]][l-y[i]]+1);
            }
        }
    }
    printf("%d",f[n][m-1]);
    k=m-1;
    while(k>0&&f[n][m-1]==f[n][k-1]){
        k--;
    }
    printf(" %d\n",m-k);
    return 0;
}

②依旧是考虑二维背包问题,但是考虑到数据范围 n ϵ [ 0 , 1000 ] , k ϵ [ 0 , 100 ] n\epsilon[0,1000],k\epsilon[0,100] nϵ[0,1000],kϵ[0,100]所以将能抓取的宝可梦数和消耗生命值作为两个维度;f[i][j]表示已经使用的精灵球数。
这样先将f初始化为正无穷INF,f[0][j]=0(因为没抓到宝可梦不消耗球),
f [ i ] [ j ] = m i n ( f [ i ] [ j ] , f [ i − 1 ] [ j − r ] + c ) f[i][j]=min(f[i][j],f[i-1][j-r]+c) f[i][j]=min(f[i][j],f[i1][jr]+c)(需保证] f [ i − 1 ] [ j − r ] + c < = n f[i-1][j-r]+c<=n f[i1][jr]+c<=n)
这样从k开始找到第一个f[ans][m-1]!=INF的ans就是最大捕捉数。
然后考虑最大生命值即最小生命消耗,同样是找f[ans][i]<=n的最小i。

//第二种
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int f[110][510];
int main(){
    int n,m,k,c,r;
    memset(f,0x3f,sizeof(f));
    scanf("%d%d%d",&n,&m,&k);
    for(int i=0;i<m;i++)
        f[0][i]=0;
    for(int i=0;i<k;i++){
        scanf("%d%d",&c,&r);
        for(int j=k;j;j--){
            for(int l=m-1;l>=r;l--){
                if(f[j-1][l-r]+c<=n){
                    f[j][l]=min(f[j][l],f[j-1][l-r]+c);
                }
            }
        }
    }
    int ans=0;
    for(int i=k;i;i--){
        if(f[i][m-1]!=0x3f3f3f3f){
            ans=i;
            break;
        }
    }
    printf("%d ",ans);
    k=m-1;
    while(k&&f[ans][k-1]<=n)  k--;
    printf("%d\n",m-k);
    return 0;
}

AcWing 278. 数字组合

题干:

给定N个正整数A1,A2,…,AN,从中选出若干个数,使它们的和为M,求有多少种选择方案。
输入格式
第一行包含两个整数N和M。
第二行包含N个整数,表示A1,A2,…,AN
输出格式
包含一个整数,表示可选方案数。

数据范围
1≤N≤100,
1≤M≤10000,
1≤Ai≤1000
输入样例:
4 4
1 1 2 2
输出样例:
3

思路:

从n个数中选择任意个的和为m的方案数。因为只能选择一次,所以看作是01背包问题,既然要求方案数那就不是+1而是+f[i-v]了,初始值为f[0]=1。f[i]表示何为i的方案数;依旧是倒退。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int f[11000];
int main(){
    int n,m,v;
    scanf("%d%d",&n,&m);
    f[0]=1;
    for(int i=0;i<n;i++){
        scanf("%d",&v);
        for(int j=m;j>=v;j--){
            f[j]+=f[j-v];
        }
    }
    printf("%d\n",f[m]);
    return 0;
}

AcWing 532. 货币系统

题干:

在网友的国度中共有 n 种不同面额的货币,第 i 种货币的面额为 a[i],你可以假设每一种货币都有无穷多张。
为了方便,我们把货币种数为 n、面额数组为 a[1…n] 的货币系统记作 (n,a)。 
在一个完善的货币系统中,每一个非负整数的金额 x 都应该可以被表示出,即对每一个非负整数 x,都存在 n 个非负整数 t[i] 满足 a[i]× t[i] 的和为 x。
然而,在网友的国度中,货币系统可能是不完善的,即可能存在金额 x 不能被该货币系统表示出。
例如在货币系统 n=3, a=[2,5,9] 中,金额 1,3 就无法被表示出来。 
两个货币系统 (n,a) 和 (m,b) 是等价的,当且仅当对于任意非负整数 x,它要么均可以被两个货币系统表出,要么不能被其中任何一个表出。 
现在网友们打算简化一下货币系统。
他们希望找到一个货币系统 (m,b),满足 (m,b) 与原来的货币系统 (n,a) 等价,且 m 尽可能的小。
他们希望你来协助完成这个艰巨的任务:找到最小的 m。
输入格式
输入文件的第一行包含一个整数 T,表示数据的组数。
接下来按照如下格式分别给出T组数据。 
每组数据的第一行包含一个正整数 n。
接下来一行包含 n 个由空格隔开的正整数 a[i]。
输出格式
输出文件共有T行,对于每组数据,输出一行一个正整数,表示所有与 (n,a) 等价的货币系统 (m,b) 中,最小的 m。

数据范围
1≤n≤100,
1≤a[i]≤25000,
1≤T≤20
输入样例:
2
4
3 19 10 6
5
11 29 13 19 17
输出样例:
2
5

思路:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int x[110];
int f[26000];
int main(){
    int t,n;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%d",&x[i]);
        }
        sort(x+1,x+n+1);
        memset(f,0,sizeof(f));
        f[0]=1;
        int mx=x[n];
        for(int i=1;i<=n;i++){
            for(int j=x[i];j<=mx;j++){
                f[j]+=f[j-x[i]];
            }
        }
        int ans=n;
        for(int i=1;i<=n;i++){
            //printf("%d\n",f[x[i]]);
            if(f[x[i]]>1)  ans--;
        }
        printf("%d\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值