背包提高练习题题解

前言


文中介绍了背包的提高练习,难度从黄到蓝。

T1-SCUBADIV - Scuba diver

题目描述

潜水员为了潜水要使用特殊的装备。

他有一种带 22 种气体的气缸:一个为氧气,一个为氮气。潜水员有 mm 个气缸。每个气缸都有重量 wiw_i 和氧气容量 oio_i 和氮气容量 nin_i

让潜水员下潜的深度需要各种的数量的氧和氮。潜水员为了完成他的工作需要特定数量的氧和氮。

一共有 qq 组数据,对于每一组数据,给出潜水员所需要的氧气量 OO 以及所需的氮气量 NN ,请问他完成工作所需气缸的总重的最低限度是多少?

输入/输出格式

输入文件的第一行,仅包含数据的组数 qq

对于接下来的每一组数据:

第一行输入潜水员所需要的氧气量 OO 以及所需的氮气量 NN

第二行输入潜水员可选的氧气瓶的数目 mm

接下来的 mm 行,每一行有三个数,从左至右依次为第 ii 个氧气瓶提供的氧气量 oio_i,氮气量 nin_i,以及第 ii 个氧气瓶的重量 wiw_i

输出文件只有一行,即为答案。(注意要换行)

数据范围与约定

  • 1O,oi21,1N,ni79,1m1000,1wi8001\leq O,o_i\leq 21,1\leq N,n_i\leq 79,1\leq m\leq 1000,1\leq w_i\leq 800

(它们都是整数)

样例 #1

样例输入 #1

1
5 60
5
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119

样例输出 #1

249

Solution

其实就是二维的背包,相当于有两个体积。

此时我们可以定义 dpi,j,kdp_{i,j,k} 为:选到第 ii 个商品,氧气容量为 jj,氮气容量为 kk 的最小重量。

由于数据较小,边界设为 500500 就好了。

难度约为黄。

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

题目描述

约翰的干草库存已经告罄,他打算为奶牛们采购 H(1H50000)H(1 \leq H \leq 50000) 磅干草。 他知道 N(1N100)N(1 \leq N\leq 100) 个干草公司,现在用 11NN 给它们编号。第 ii 公司卖的干草包重量为 Pi(1Pi5,000)P_i (1 \leq P_i \leq 5,000) 磅,需要的开销为 Ci(1Ci5,000)C_i (1 \leq C_i \leq 5,000) 美元。每个干草公司的货源都十分充足, 可以卖出无限多的干草包。

帮助约翰找到最小的开销来满足需要,即采购到至少 HH 磅干草。

样例 #1

样例输入 #1

2 15 
3 2 
5 3

样例输出 #1

9

Solution

与一般题不同的是,本题要求干草数量(体积)至少为 HH。此时就要考虑比 HH 大的情况的开销,即 j>Hj>H。有一个结论很重要:

  • 最优解存在于 0H+max{Pi}0\sim H+\max\{P_i\}

因为 j>Hj>H 的情况只会在原来比 HH 小,加入某个商品后比 HH 大时出现。若本来 j>Hj>H,再选商品的花费必定没有不选优。

在本题中为 5×104+5×1035\times 10^4+5\times 10^3。减少了不必要的时空开销。

难度约为黄。

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

NN 个奶牛。有重量 WiW_i 和才艺值 ViV_i。现组成奶牛小队,你需要找到一队重量至少为 MM 的奶牛,使得他们的才艺值最大。

输入格式

n m
W1 W2...Wn
V1 V2...Vn

样例输入 #1

3 15

20 10 30

21 11 31

样例输出 #1

63

数据规模与约定

对于全部的测试点,保证: 1N250,1M10000,1Wi106,103Vi1031\le N\le 250,1\leq M\leq 10000,1\leq W_i\leq 10^6,-10^3\leq V_i\leq 10^3


Solution

本题的重点也是边界,本题的最优解并不存在上述结论。并且 WiW_i 极大。

此时我们就要重新构建状态:

我们依旧设 dpi,jdp_{i,j} 为选到了第 ii 个奶牛,重量为 jj 的最大才艺值。
但是对于 dpMdp_M,我们需要特别对待,定义为重量 M\ge M 的最大才艺值。

因为重量必然是递增的。dpMdp_M 的状态选或不选都是 M\ge M 的。


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 手上有 NN 道题,他总共有 TT 的时间来完成他们中的一些或全部。每道题有一个完成所需时间 tit_i 和一个难度系数 cic_i。如果 zql 在剩余 xx 个单位时间的时候开始做题 i,并且能够完成,那么总分加上 x×cix\times c_i。现在 zql 要从这 NN 道题中选出一些在 TT 个单位时间内完成,并且按照某种顺序依次完成它们(zql 每个单位时间只能做一道题,并且一旦他决定做某题就会一直做直到做完),那么他最多能够拿到多少分呢?


对于 100%100\% 的数据,N3000,T10000N≤3000,T≤10000,所有 tit_icic_i 均不超过 100100。保证答案在 3232 位有符号整型范围内。

输入样例:

3 12

3 6

7 5

4 2

输出样例:

117

Solution

在题目中,做题的顺序,即剩下的时间与权值是有关系的。所以要考虑一个顺序,使得最优解存在于该顺序中。

假设现在有第 i,ji,j 个商品,剩余时间为 TT。此时分先后顺序讨论价值。

  • ii:T×ci+(Tti)×cj=T×ci+T×cjti×cjT\times c_i+(T-t_i)\times c_j=T\times c_i+T\times c_j-t_i\times c_j

  • jj:T×cj+(Ttj)×ci=T×ci+T×cjtj×ciT\times c_j+(T-t_j)\times c_i=T\times c_i+T\times c_j-t_j\times c_i


得到排序顺序:ti×cj<tj×cit_i\times c_j<t_j\times c_i。(减的越小,价值越大)


然后就是 01 背包的模板了。对第 ii 个商品做选与不选。

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 和好朋友们要去爬山啦!

他们一共有 KK 个人,每个人都会背一个包。这些包 的容量是相同的,都是 VV。可以装进背包里的一共有 NN 种物品,每种物品都有 给定的体积和价值。

在 DD 看来,合理的背包安排方案是这样的: 每个人背包里装的物品的总体积恰等于包的容量。 每个包里的每种物品最多只有一件,但两个不同的包中可以存在相同的物品。

任意两个人,他们包里的物品清单不能完全相同。 在满足以上要求的前提下,所有包里的所有物品的总价值最大是多少呢?

输入格式

第一行三个数 KKVVNN

接下来每行两个数,表示体积和价值

输出格式

kk 优解的价值和

样例输入

2 10 5
3 12
7 20
2 4
5 6
1 1

样例输出

57

提示

对于 100%100\%的数据, K50,V5000,N200K\le 50,V\le 5000,N\le 200


本题是一道经典的求背包 kk 优解的问题。

我们定义 dpi,j,kdp_{i,j,k} 为选到了第 ii 个商品,价值最多为 jj 的第 kk 优解。

对于每个 i,ji,jdpi,jdp_{i,j} 可以从 dpi1,j,dpi1,jwi+vidp_{i-1,j},dp_{i-1,j-{w_i}}+v_i 转移过来。
对于第 kk 优解,每个都从两种情况转移。我们需要统计所有的情况,并按价值赋给新的最优解。


但是还需要排序。考虑优化。

kk 优解的性质是:价值从大到小,也就是说具有单调性。我们可以把 dpi1,j,dpi1,jwi+vidp_{i-1,j},dp_{i-1,j-{w_i}}+v_i 分开计算。

就像归并排序一样,有两个有序的序列,使用两个指针,就可以合并为一个有序的序列。


至此,我们就解决了背包中的 kk 优解问题。本题将 kk 优解求和就可以了。

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-剪草

NN 棵草,高度为 hih_i。以下时刻内会发生以下事件:

  1. 每棵小草都长高了,第 ii 棵小草长高的高度是 gig_i
  2. 选中一棵小草并将高度变为 00,它在下一秒还会生长。
  3. 计算这 NN 棵草的高度和,如果不超过 HH,则完成任务,否则轮到下一时刻。

问最早在什么时候可以完成任务。如果永远完不成,输出 1-1


输入格式

N H
h1 h2...hn
g1 g2...gn

输出格式

最早的完成时刻。

数据范围

1n50,0H106,0hi105,1gi1051\le n\le 50,0\le H\le 10^6,0\le h_i\le 10^5,1\le g_i \le 10^5

输入样例

3 16

5 8 58

2 1 1

输出样例

1

Solution

这题的背包也和贪心结合了起来。

与 T5 一样,选择的次序也会对结果产生影响。先来考虑顺序。有 i,ji,j 两棵草,设都在第 ss 时刻,按先后顺序比较剪掉草的高度和:

  • ii:hi+hj+gi×s+gj×(s+1)=hi+hj+(gi+gj)×s+gjh_i+h_j+g_i \times s+g_j\times (s+1)=h_i+h_j+(g_i+g_j)\times s+g_j
  • jj:hi+hj+gj×s+gi×(s+1)=hi+hj+(gi+gj)×s+gih_i+h_j+g_j \times s+g_i\times (s+1)=h_i+h_j+(g_i+g_j)\times s+g_i

消去相同部分,想要高度和更大,就要使 gj>gig_j>g_i

排序后,进行背包。设 dpi,jdp_{i,j} 为选到了第 ii 个草时,剪了 jj 次草的最大剪草高度。这样就可以对第 ii 个草剪与不剪。剪的情况:dpj1+hi+j×gidp_{j-1}+h_i+j\times g_i


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 最近迷上了在上课时玩《黄金矿工》这款游戏。为了避免被老师发现,他必须小心翼翼,因此他总是输。

在输掉自己所有的金币后,他向你求助。每个黄金可以看做一个点(没有体积)。现在给出你 NN 个黄金的坐标,挖到它们所需要的时间以及它们的价值。有些黄金在同一条直线上,这时候你必须按顺序挖。你可以瞬间把钩子转到任意角度。

请你帮助小 A 算出在时间 TT 内他最多可以得到多少价值的金子。

输入格式

第一行两个整数 NNTT,表示黄金的个数和总时间。

接下来 NN 行,每行四个整数 xxyyttvv,分别表示黄金的坐标,挖到这个黄金的时间,以及这个黄金的价值。

输出格式

一个整数,表示你可以在 TT 时间内得到的最大价值。

样例 #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

提示

  • 对于 30%30\% 的数据,0<T4×1030<T\le 4\times 10^3
  • 对于 100%100\% 的数据,1N2001\le N\le 2000<T4×1040<T\le 4\times 10^4

保证 0x2000\le|x|\le 2000<y2000<y≤2000<t2000<t\le 2000v2000\le v\le 200


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;
}

T9-P2967

看我的题解

本文作者:cjrqwq

本文链接:https://www.cnblogs.com/yfzqwq/p/18492811

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   cjrqwq  阅读(4)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
展开
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.