20200404(背包)
1、搭配购买
一道并查集加上01背包的题目,通过并查集的方式将属于一个集合的物品封装成一个物品,然后就跟01背包做法相似。
【问题描述】 Joe 觉得云朵很美,决定去山上的商店买一些云朵。商店里有 n 朵云,云朵被编 号为 1,2,…...,n,并且每朵云都有一个价值。但是商店老板跟他说,一些云 朵要搭配来买才好,所以买一朵云则与这朵云有搭配的云都要买。 但是 Joe 的钱有限,所以他希望买的价值越多越好。 【输入说明】 第 1 行 n,m,w,表示 n 朵云,m 个搭配,Joe 有 w 的钱。 第 2~n+1 行,每行 ci,di 表示 i 朵云的价钱和价值。 第 n+2~n+1+m 行,每行 ui,vi,表示买 ui 就必须买 vi,同理,如果买 vi 就必须 买 ui。【输出说明】 一行,表示可以获得的最大价值。 【输入样例】 5 3 10 3 10 3 10 3 10 5 100 10 1 1 3 3 2 4 2 【输出样例】 1 【数据范围】 30%的数据保证:n<=100 50%的数据保证:n<=1,000;m<=100;w<=1,000 100%的数据保证:n<=10,000;0<=m<=5000;w<=10,000
#include <bits/stdc++.h> using namespace std; const int maxx=1e4+5; int c[maxx],d[maxx],f[maxx]; int fa[10005],pri[10005],val[10005]; int find(int x) { return (fa[x]!=x)?fa[x]=find(fa[x]):x; } int main() { int n,m,w,u,v; scanf("%d %d %d",&n,&m,&w); for(int i=1;i<=n;i++) { scanf("%d %d",&c[i],&d[i]); fa[i]=i; } for(int i=1;i<=m;i++) { scanf("%d %d",&u,&v); int x=find(u); int y=find(v); if(x!=y) fa[x]=y; } for(int i=1;i<=n;i++) { int x=find(i); pri[x]+=c[i];//同一个集合里面的总费用 val[x]+=d[i];//总价值 } int count=0; for(int i=1;i<=n;i++) { if(fa[i]==i) { pri[++count]=pri[i]; val[count]=val[i]; } }//封装成一个物品 for(int i=1;i<=count;i++) for(int j=w;j>=pri[i];j--) f[j]=max(f[j],f[j-pri[i]]+val[i]);//01背包 printf("%d",f[w]); return 0; }
2、Dima and Salad
n个物品,k为倍数。每个物品有两个属性(ai和bi),求在满足所取物品的a属性和是b属性和的k倍的前提下,问a属性的最大值是多少
按照题意,就是要让我们选出一些组aibi,使得:
(a1+a2+...+aj)/(b1+b2+...+bj)=k,
然后移项,得a1+a2+...+aj=k(b1+b2+...+bj)
得(a1−b1k)+(a2−b2k)+...+(aj−bjk)=0(a1−b1k)+(a2−b2k)+...+(aj−bjk)=0
观察公式,发现这其实是一个容量为0的01背包,
我们可以把ai−bik看作一个物品的体积,ai看作价值,然后一个标准的01背包模板
那么,背包的容量?容量为0怎么枚举呢?
先来想一想,(ai−bik)是不是有可能为负数?那么怎么办呢?
可以考虑开两个背包,容量分别为V和-V,那么加起来就抵消为0,正容量的背包处理正体积的,负容量的背包处理负体积的
【问题描述】 在冰箱里有 n 个水果,每种水果都有两个参数:味道和热量。现在要从冰箱里拿 些水果出来做一份水果沙拉。选择水果时遵循一定的原则:所选水果的总味道与 总卡路里的比率必须等于 k。 请计算在这种原则下,所选水果的最大味道值是什么? 【输入说明】 输入的第一行包含两个整数 n,k(1 ≤ n ≤ 100, 1 ≤ k ≤ 10)。 输入的第二行包含 n 个整数 a1, a2, …, an(1 ≤ ai ≤ 100),表 示每种水果的味道值。 输入的第三行包含 n 个整数 b1, b2, …, bn(1 ≤ bi ≤ 100),表 示每种水果的卡路里,ai 和 bi 是一一对应的。 【输出说明】 如果无法选择沙拉所需的水果,请在第 1 行输出-1。 否则输出一个整数,即所选水果味道值的最大可能总和。 【输入样例 1】 3 2 10 8 1 2 7 1 【输出样例 1】 18 【输入样例 2】 5 3 4 4 4 4 4 2 2 2 2 2 【输出样例 2】 -1
#include <bits/stdc++.h> using namespace std; const int N=1e6+5; const int inf = 1e8; const int maxx=10000; int f[N],g[N]; int a[110],b[110]; int n,k; int main() { int num; scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=n;i++) scanf("%d",&b[i]); for(int i=1;i<=maxx;i++) f[i]=g[i]=-inf ; for ( int i=1;i<=n;i++ ) { num=a[i]-b[i]*k; if(num>=0) { for(int j=maxx;j>=num;j--) f[j]=max(f[j],f[j-num]+a[i]);//为正数的时候 } else{ for(int j=maxx;j>=-num;j--) g[j]=max(g[j],g[j+num]+a[i]);//为负数的时候 } } int ans=-1; for(int i=maxx;i>=0;i-- ) ans=max(ans,f[i]+g[i]); if(ans<=0) ans=-1 ; cout<<ans<<endl; return 0; }
3、最佳策略
其实就是一个分组背包
【问题描述】 经历了无数次编程竞赛失败以后,小明明白了一个道理:做一题就要对一题!但 是要完全正确地做对一题是要花很多时间(包括调试时间),而竞赛的时间有限。 所以开始做题之前最好先认真审题,估算一下每一题如果要完全正确地做出来所 需要的时间,然后选择有把握的题目先做。如果做完了预先选择的题目之后还 有时间,但是这些时间又不足以完全解决一道题目,应该把其他的题目用贪心之 类的算法随便做做,争取“骗”一点分数。 【输入说明】 第 1 行,两个正整数 N 和 T,表示题目的总数以及竞赛的时限(秒) 以下的 N 行,每行四个正整数 W1、T1、W2、T2,分别表示第 i 题完全正确做 出来的得分,完全正确做出来所花费的时间(秒),“骗”来的分数,“骗”分所 花费的时间(秒)。 【输出说明】 根据每一题解题时间的估计值,确定一种做题方案(即哪些题目认真做,哪些题 目“骗”分,哪些不做),使能在限定的时间内获得最高的得分。 【数据范围】 3≤N≤30,2≤T≤1.08×10^6,1≤W1i、W2i≤3×10^4,1≤Tli、T2i≤T 【输入样例 1】 4 10800 18 3600 3 1800 22 4000 12 3000 28 6000 0 3000 32 8000 24 6000 【输出样例 1】 50 【输入样例 2】 3 7200 50 5400 10 900 50 7200 10 900 50 5400 10 900 【输出样例 2】 70
#include <bits/stdc++.h> using namespace std; const int N=1e6+5; long long f[N],s[35][4],w[35][4]; int n,m; int main(){ cin>>n>>m; for(int i=1;i<=n;i++) scanf("%d %d %d %d",&s[i][1],&w[i][1],&s[i][2],&w[i][2]); for(int i=1;i<=n;i++) for(int j=m;j>=0;j--) for(int k=1;k<=2;k++){ if(j>=w[i][k]){ f[j]=max(f[j],f[j-w[i][k]]+s[i][k]); } } cout<<f[m]<<endl; return 0; }
4、升级游戏
就是一个多组数据的二维费用背包,状态表示为f[i][j]:kill i个怪 消耗的厌恶值不超过j的最大经验值
【问题描述】 最近有一款叫做 FATE 的游戏,Joe 为了得到极品装备,他需要不停的杀怪做任 务。久而久之开始对杀怪产生厌恶感,但又不得不通过杀怪来升完这最后一级。 现在的问题是,升掉最后一级还需 n 的经验值,他还留有 m 的忍耐度,每杀一 个怪会得到相应的经验,并减掉相应的忍耐度。当忍耐度降到 0 或者 0 以下时, 他就不会玩这游戏。Joe 还说了他最多只杀 s 只怪。请问他能升掉这最后一级吗? 【输入说明】 输入数据有多组,对于每组数据第一行输入 n,m,k,s(0 < n,m,k,s < 100)四个正 整数。分别表示还需的经验值,保留的忍耐度,怪的种数和最多的杀怪数。接下 来输入 k 行数据。每行数据输入两个正整数 a,b(0 < a,b < 20);分别表示杀掉一 只这种怪会得到的经验值和会减掉的忍耐度。(每种怪都有无数个) 【输出说明】 输出升完这级还能保留的最大忍耐度,如果无法升完这级输出-1。 【输入样例】 10 10 1 10 1 1 10 10 1 9 1 1 9 10 2 10 1 1 2 2 【输出样例】 0 -1 1
#include <bits/stdc++.h> using namespace std; const int N=1005; int f[N][N],a[N],b[N]; int n,m,k,s; int main() { while(cin>>n>>m>>k>>s){ memset(f,0,sizeof(f)); memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); for(int i=1;i<=k;i++){ scanf("%d %d",&a[i],&b[i]); } for(int i=1;i<=k;i++) for(int j=1;j<=s;j++)//做多能杀的怪物 for(int l=b[i];l<=m;l++)//因为每种怪物无限个,完全背包 f[j][l]=max(f[j][l],f[j-1][l-b[i]]+a[i]); int ans=-1; for(int i=0;i<=m;i++){ if(f[s][i]>=n) { ans=i; break; } } if(ans==-1) printf("-1\n"); else printf("%d\n",m-ans); } return 0; }