背包提高练习题题解
前言
文中介绍了背包的提高练习,难度从黄到蓝。
T1-SCUBADIV - Scuba diver
题目描述
潜水员为了潜水要使用特殊的装备。
他有一种带 种气体的气缸:一个为氧气,一个为氮气。潜水员有 个气缸。每个气缸都有重量 和氧气容量 和氮气容量 。
让潜水员下潜的深度需要各种的数量的氧和氮。潜水员为了完成他的工作需要特定数量的氧和氮。
一共有 组数据,对于每一组数据,给出潜水员所需要的氧气量 以及所需的氮气量 ,请问他完成工作所需气缸的总重的最低限度是多少?
输入/输出格式
输入文件的第一行,仅包含数据的组数 。
对于接下来的每一组数据:
第一行输入潜水员所需要的氧气量 以及所需的氮气量 ;
第二行输入潜水员可选的氧气瓶的数目 ;
接下来的 行,每一行有三个数,从左至右依次为第 个氧气瓶提供的氧气量 ,氮气量 ,以及第 个氧气瓶的重量 。
输出文件只有一行,即为答案。(注意要换行)
数据范围与约定
(它们都是整数)
样例 #1
样例输入 #1
1
5 60
5
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
样例输出 #1
249
Solution
其实就是二维的背包,相当于有两个体积。
此时我们可以定义 为:选到第 个商品,氧气容量为 ,氮气容量为 的最小重量。
由于数据较小,边界设为 就好了。
难度约为黄。
Code
#include<bits/stdc++.h>
using namespace std;
const int N =1e3+10;
int t,a,n,w1,w2,v,dp[N][N],ans=1e9;
int main() {
cin>>t>>a>>n;
memset(dp,0x3f,sizeof dp);dp[0][0]=0;
for(int i=1;i<=n;i++) {
cin>>w1>>w2>>v;
for(int j=500;j>=w1;j--)
for(int k=500;k>=w2;k--) {
dp[j][k]=min(dp[j][k],dp[j-w1][k-w2]+v);
if(j>=t&&k>=a&&dp[j][k]<1e9) ans=min(ans,dp[j][k]);
}
}
cout<<ans;
return 0;
}
T2-[USACO08NOV] Buying Hay S
题目描述
约翰的干草库存已经告罄,他打算为奶牛们采购 磅干草。 他知道 个干草公司,现在用 到 给它们编号。第 公司卖的干草包重量为 磅,需要的开销为 美元。每个干草公司的货源都十分充足, 可以卖出无限多的干草包。
帮助约翰找到最小的开销来满足需要,即采购到至少 磅干草。
样例 #1
样例输入 #1
2 15
3 2
5 3
样例输出 #1
9
Solution
与一般题不同的是,本题要求干草数量(体积)至少为 。此时就要考虑比 大的情况的开销,即 。有一个结论很重要:
- 最优解存在于 。
因为 的情况只会在原来比 小,加入某个商品后比 大时出现。若本来 ,再选商品的花费必定没有不选优。
在本题中为 。减少了不必要的时空开销。
难度约为黄。
Code
#include<bits/stdc++.h>
using namespace std;
const int N =5e5+10;
int t,a,n,w,v,dp[N],ans=1e6;
int main() {
cin>>n>>t;
memset(dp,0x3f,sizeof dp);dp[0]=0;
for(int i=1;i<=n;i++) {
cin>>w>>v;
for(int j=w;j<=5e4+5000;j++) {
dp[j]=min(dp[j],dp[j-w]+v);
if(j>=t) ans=min(ans,dp[j]);
}
}
cout<<ans;
return 0;
}
T3-Talent Show
有 个奶牛。有重量 和才艺值 。现组成奶牛小队,你需要找到一队重量至少为 的奶牛,使得他们的才艺值最大。
输入格式
n m
W1 W2...Wn
V1 V2...Vn
样例输入 #1
3 15
20 10 30
21 11 31
样例输出 #1
63
数据规模与约定
对于全部的测试点,保证: 。
Solution
本题的重点也是边界,本题的最优解并不存在上述结论。并且 极大。
此时我们就要重新构建状态:
我们依旧设 为选到了第 个奶牛,重量为 的最大才艺值。
但是对于 ,我们需要特别对待,定义为重量 的最大才艺值。
因为重量必然是递增的。 的状态选或不选都是 的。
Code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,ma,w[N],v[N],dp[N];
int main() {
cin>>n>>ma;
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=ma;j>=0;j--) {
dp[min(j+w[i],ma)]=max(dp[min(j+w[i],ma)],dp[j]+v[i]);
}
}
cout<<dp[ma];
return 0;
}
T4-得分
题目描述
现在 zql 手上有 道题,他总共有 的时间来完成他们中的一些或全部。每道题有一个完成所需时间 和一个难度系数 。如果 zql 在剩余 个单位时间的时候开始做题 i,并且能够完成,那么总分加上 。现在 zql 要从这 道题中选出一些在 个单位时间内完成,并且按照某种顺序依次完成它们(zql 每个单位时间只能做一道题,并且一旦他决定做某题就会一直做直到做完),那么他最多能够拿到多少分呢?
对于 的数据,,所有 和 均不超过 。保证答案在 位有符号整型范围内。
输入样例:
3 12
3 6
7 5
4 2
输出样例:
117
Solution
在题目中,做题的顺序,即剩下的时间与权值是有关系的。所以要考虑一个顺序,使得最优解存在于该顺序中。
假设现在有第 个商品,剩余时间为 。此时分先后顺序讨论价值。
-
先 :
-
先 :
得到排序顺序:。(减的越小,价值越大)
然后就是 01 背包的模板了。对第 个商品做选与不选。
Code
#include<bits/stdc++.h>
#define w(i) w[mp[i]]
#define v(i) v[mp[i]]
using namespace std;
const int N = 1e5+10;
int n,m,w[N],v[N],mp[N],ans;
int dp[N];//最多剩余 j 时间
int main() {
cin>>n>>m;
for(int i=1;i<=n;i++) {
cin>>w[i]>>v[i];
mp[i]=i;
}
sort(mp+1,mp+n+1,[](int a,int b) {
return w[a]*v[b]<w[b]*v[a];
});
for(int i=1;i<=n;i++) {
for(int j=0;j+w(i)<=m;j++) {
dp[j]=max(dp[j],dp[j+w(i)]+(j+w(i))*v(i));
ans=max(dp[j],ans);//剩余多少时间都可以统计答案
}
}
cout<<ans;
return 0;
}
T5-多人背包
题目描述
求01背包前k优解的价值和
DD 和好朋友们要去爬山啦!
他们一共有 个人,每个人都会背一个包。这些包 的容量是相同的,都是 。可以装进背包里的一共有 种物品,每种物品都有 给定的体积和价值。
在 DD 看来,合理的背包安排方案是这样的: 每个人背包里装的物品的总体积恰等于包的容量。 每个包里的每种物品最多只有一件,但两个不同的包中可以存在相同的物品。
任意两个人,他们包里的物品清单不能完全相同。 在满足以上要求的前提下,所有包里的所有物品的总价值最大是多少呢?
输入格式
第一行三个数 、、
接下来每行两个数,表示体积和价值
输出格式
前 优解的价值和
样例输入
2 10 5
3 12
7 20
2 4
5 6
1 1
样例输出
57
提示
对于 的数据,
本题是一道经典的求背包 优解的问题。
我们定义 为选到了第 个商品,价值最多为 的第 优解。
对于每个 , 可以从 转移过来。
对于第 优解,每个都从两种情况转移。我们需要统计所有的情况,并按价值赋给新的最优解。
但是还需要排序。考虑优化。
优解的性质是:价值从大到小,也就是说具有单调性。我们可以把 分开计算。
就像归并排序一样,有两个有序的序列,使用两个指针,就可以合并为一个有序的序列。
至此,我们就解决了背包中的 优解问题。本题将 优解求和就可以了。
Code
#include<bits/stdc++.h>
using namespace std;
int K,ma,n,dp[5001][51],w,v,t[51],ans;
int main() {
cin>>K>>ma>>n;
memset(dp,-0x3f,sizeof dp);
dp[0][1]=0;
for(int i=1;i<=n;i++) {
cin>>w>>v;
for(int j=ma;j>=w;j--) {
int l1=1,l2=1;
for(int p=1;p<=K;p++)
if(dp[j][l1]>dp[j-w][l2]+v)
t[p]=dp[j][l1++];
else
t[p]=dp[j-w][l2++]+v;
for(int p=1;p<=K;p++) dp[j][p]=t[p];
//要建立一个临时数组 t,如果随用随改的话可能就会重复计算
}
}
for(int i=1;i<=K;i++) ans+=dp[ma][i];
cout<<ans;
return 0;
}
T6-Bank notes
多重背包二进制优化可以通过本题。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int n,m,c;
int w[N],tt[N],v[N],dp[N];
void add(int _w,int _v,int _c) {
int cnt=1;
while(_c) {
if(_c<cnt) cnt=_c;
// cout<<cnt<<" "<<cnt*_w<<" "<<cnt*_v<<"\n";
_c-=cnt,w[++c]=cnt*_w,v[c]=cnt*_v;cnt<<=1;
}
}
int main() {
cin>>n;
for(int i=1;i<=n;i++) cin>>tt[i];
for(int i=1,t;i<=n;i++) cin>>t,add(tt[i],1,t);
memset(dp,0x3f,sizeof dp);dp[0]=0;
cin>>m;
for(int i=1;i<=c;i++) {
for(int j=m;j>=w[i];j--) {
dp[j]=min(dp[j],dp[j-w[i]]+v[i]);
}
}
cout<<dp[m];
return 0;
}
T7-剪草
有 棵草,高度为 。以下时刻内会发生以下事件:
- 每棵小草都长高了,第 棵小草长高的高度是 。
- 选中一棵小草并将高度变为 ,它在下一秒还会生长。
- 计算这 棵草的高度和,如果不超过 ,则完成任务,否则轮到下一时刻。
问最早在什么时候可以完成任务。如果永远完不成,输出 。
输入格式
N H
h1 h2...hn
g1 g2...gn
输出格式
最早的完成时刻。
数据范围
输入样例
3 16
5 8 58
2 1 1
输出样例
1
Solution
这题的背包也和贪心结合了起来。
与 T5 一样,选择的次序也会对结果产生影响。先来考虑顺序。有 两棵草,设都在第 时刻,按先后顺序比较剪掉草的高度和:
- 先 :
- 先 :
消去相同部分,想要高度和更大,就要使 。
排序后,进行背包。设 为选到了第 个草时,剪了 次草的最大剪草高度。这样就可以对第 个草剪与不剪。剪的情况:。
Code
#include<bits/stdc++.h>
#define h(i) h[mp[i]]
#define g(i) g[mp[i]]
using namespace std;
const int N = 1e5+10;
int n,s,s1,s2,h[N],g[N],mp[N],dp[N];
//第 j 时刻减的最多草
int main() {
cin>>n>>s;
for(int i=1;i<=n;i++)
cin>>h[i],s1+=h[i],mp[i]=i;
for(int i=1;i<=n;i++)
cin>>g[i],s2+=g[i];
sort(mp+1,mp+n+1,[](int a,int b) {
return g[a]<g[b];
});
for(int i=1;i<=n;i++) {
for(int j=i;j>0;j--) {
dp[j]=max(dp[j],dp[j-1]+h(i)+j*g(i));//j 不剪或剪
}
}
for(int i=0;i<=n;i++)//在第 i 个时刻,判断总共的高度减去剪掉的高度满足条件与否
if(s1+s2*i-dp[i]<=s) return 0&(printf("%d",i));
cout<<-1;
return 0;
}
T8-[TJOI2013] 黄金矿工
题目描述
小 A 最近迷上了在上课时玩《黄金矿工》这款游戏。为了避免被老师发现,他必须小心翼翼,因此他总是输。
在输掉自己所有的金币后,他向你求助。每个黄金可以看做一个点(没有体积)。现在给出你 个黄金的坐标,挖到它们所需要的时间以及它们的价值。有些黄金在同一条直线上,这时候你必须按顺序挖。你可以瞬间把钩子转到任意角度。
请你帮助小 A 算出在时间 内他最多可以得到多少价值的金子。
输入格式
第一行两个整数 和 ,表示黄金的个数和总时间。
接下来 行,每行四个整数 、、、,分别表示黄金的坐标,挖到这个黄金的时间,以及这个黄金的价值。
输出格式
一个整数,表示你可以在 时间内得到的最大价值。
样例 #1
样例输入 #1
3 10
1 1 1 1
2 2 2 2
1 3 15 9
样例输出 #1
3
样例 #2
样例输入 #2
3 10
1 1 13 1
2 2 2 2
1 3 4 7
样例输出 #2
7
提示
- 对于 的数据,;
- 对于 的数据,,。
保证 ,,,。
Solution
这题关键在于将坐标系中的值,转变为背包问题的模型。我们可以枚举一个开始点,然后往后找在一条直线的点。
此时要考虑如何做到按顺序取,可以令每一条直线为一组,从起点开始做一个前缀和,然后分组背包,意义在于可以实现按顺序取。
Code
#include<bits/stdc++.h>
using namespace std;
const int N = 201,M = 4e4+10;
int n,m,ti,tj,x,y,c;
int mp[N<<1][N],mpp[N<<1][N],w[N][N],v[N][N],cn,cy[N],dp[M];
int main() {
cin>>n>>m;
for(int i=1,d;i<=n;i++) {
cin>>x>>y>>c>>d;
x+=200;//x有负数 防越界
mp[x][y]=d;
mpp[x][y]=c;
}
for(int i=-200,p=i+200;i<=200;i++,p++) for(int j=0;j<=200;j++)
if(mp[p][j]) {
++cn,cy[cn]=1;
w[cn][1]=mp[p][j];
v[cn][1]=mpp[p][j];//更新价值
mp[p][j]=0;
for(int i2=i,p2=p;i2<=200;i2++,p2++) for(int j2=j;j2<=200;j2++)
if(i*j2==j*i2&&abs(j)<abs(j2)&&mp[p2][j2]) {
++cy[cn];
w[cn][cy[cn]]=w[cn][cy[cn]-1]+mp[p2][j2];
v[cn][cy[cn]]=v[cn][cy[cn]-1]+mpp[p2][j2];//前缀和
mp[p2][j2]=0;//当前点已选
j2=200;//只能是斜着的,所以直线搜索不可能有符合条件的点
}
}
for(int i=1;i<=cn;i++)
for(int j=m;j>=0;j--)
for(int k=1;k<=cy[i];k++)
if(j>=v[i][k])//分组背包
dp[j]=max(dp[j],dp[j-v[i][k]]+w[i][k]);
cout<<dp[m];
return 0;
}

浙公网安备 33010602011771号