背包专项总结

T 1 : P 2722 \tt T1:P2722 T1:P2722

完全背包板子题,因为一类题可以重复选择,不再过多赘述。
容量为 m m m,物品 t i t_i ti,价值 p i p_i pi,答案 d p m dp_m dpm,注意枚举容量要正序循环
状态: d p j dp_j dpj 表示背包放入容量为 j j j 的物品的最大值
状态转移方程:
d p j = max ⁡ ( d p j , d p j − t i + p i ) dp_j=\max(dp_j,dp_{j-t_i}+p_i) dpj=max(dpj,dpjti+pi)
代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,dp[10005],w[10005],val[10005];
int main()
{
	cin>>m>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>val[i]>>w[i];
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=w[i];j<=m;j++)
		{
			dp[j]=max(dp[j],dp[j-w[i]]+val[i]);
		}
	}
	cout<<dp[m];
	return 0;
 } 

T 2 : P 1802 \tt T2:P1802 T2:P1802

本题01背包,近乎于一个板子,一个物品要么选,要么不选。
x x x 为容量, u s e i use_i usei 为物品, w i n i win_i wini l o s e i lose_i losei 皆为价值。
注意1:本题不仅能打败有经验(物品放得下),且打不败也有经验(物品放不下),所以要特判。
状态转移方程:

  • 放得下:
    d p j = m a x ( d p j + l o s e i , d p j − u s e i + w i n i ) dp_j=max(dp_j+lose_i,dp_{j-use_i}+win_i) dpj=max(dpj+losei,dpjusei+wini)
  • 放不下:
    d p j = d p j + l o s e i dp_j=dp_j+lose_i dpj=dpj+losei

注意2:因为最后要 × 5 \times5 ×5,记得开long long。
代码:

#include<iostream>
using namespace std;
const int N=1005;
long long n,x,lose[N],win[N],w[N],dp[N];
int main()
{
	cin>>n>>x;
	for(int i=1;i<=n;i++)
	{
		cin>>lose[i]>>win[i]>>w[i];
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=x;j>=0;j--)
		{
			if(j>=w[i])
			{
				dp[j]=max(dp[j]+lose[i],dp[j-w[i]]+win[i]);
			}
			else
			{
				dp[j]=dp[j]+lose[i];
			}
		}
	}
	cout<<dp[x]*5;
	return 0;
}

T 3 : P 2340 \tt T3:P2340 T3:P2340

基本做法

  • 它的容量为情商的和与智商的和,这类题可以见此题

但不过这样做时间复杂度会上天,假设智商情商全部 1000 1000 1000,有 400 400 400 头奶牛,那么总共容量就为 400 ⋅ 1000 400\cdot1000 4001000,也就是 40 40 40 万。
三重循环,第 1 1 1 层枚举物品 400 400 400 次,第 2 2 2 层枚举智商 400000 400000 400000 次,情商同理,那么总时间复杂度为 O ( 64 ⋅ 1 0 12 ) O(64\cdot10^{12}) O(641012),恐怖如斯。

优化做法

  • 我们可以将奶牛的智商视为重量,而情商视为价值(的确,这看起来很别扭)。
  • 那样,则状态为: d p j dp_j dpj 表示每个物品的重量不超过 j j j 的做大价值。
  • 答案同样麻烦,我们还要枚举到总容量,如果 d p i dp_i dpi 是个正数,那么就与 a n s ans ans 取最值:
    a n s = max ⁡ ( a n s , d p i + i ) ans=\max(ans,dp_i+i) ans=max(ans,dpi+i)
    细节:
  1. 下标不可以为 0 0 0,所以要加上一个值。
  2. 智商负数和整数分开讨论,正数倒序,负数正序。
  3. 因为每个物品可能有负数,所以将 d p dp dp 数组统一赋值为极小值。(如果用 m e m s e t \tt memset memset 的话,那么就用 − 0 x 3 f \tt -0x3f 0x3f 0 x c f \tt 0xcf 0xcf
  4. d p dp dp 数组的最后一个值设为 0 0 0,不然都会是个负数。
    代码:
#include <bits/stdc++.h>
using namespace std;
const int N=8e5+5,d=4e5;
int n,iq[405],eq[405],dp[N],ziq;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>iq[i]>>eq[i];
	memset(dp,-0x3f,sizeof dp);
	dp[d]=0;
	for(int i=1;i<=n;i++){
		if(iq[i]>=0)
			for(int j=d<<1;j>=iq[i];j--)
				dp[j]=max(dp[j],dp[j-iq[i]]+eq[i]);
		else
			for(int j=0;j<=(d<<1)+iq[i];j++)
				dp[j]=max(dp[j],dp[j-iq[i]]+eq[i]);
	}
	int ans=0;
	for(int i=d;i<=d<<1;i++)
		if(dp[i]>=0)
			ans=max(ans,dp[i]+i-d);
	cout<<ans;
	return 0;
}

T 4 : P 1853 \tt T4:P1853 T4P1853

又是一个近乎于完全背包板子的题,考试时以为是 5 5 5 倍经验日的变形,把最后的答案 × n \times n ×n 再加上本金 s s s 即可。
但不过每年的利息是可以加上本金再做下一年的本金,这种叫做复利

所以只需要枚举年份,每次都做完全背包,在将每年的利息加本金累加,最后输出计数器即可。
优化:这样做的话最后一个点会被 Hack 掉,但通过仔细观察,题目说“ a a a 一定是 1000 1000 1000 的倍数”,所以我们只需将每次与 a a a s s s 相关的皆 ÷ 1000 \div 1000 ÷1000 即可。
代码:

#include<bits/stdc++.h>
using namespace std;
int s,n,d,w[15],val[15],dp[10000005];
int main(){
	cin>>s>>n>>d;
	for(int i=1;i<=d;i++)
	{
		cin>>w[i]>>val[i];
	}
	for(int year=1;year<=n;year++)
	{
		for(int i=1;i<=d;i++)
		{
			for(int j=w[i]/1000;j<=s/1000;j++)
			{
				dp[j]=max(dp[j],dp[j-w[i]/1000]+val[i]);
			}
		}
		s+=dp[s/1000];
	}
	cout<<s;
	return 0;
}

T 5 : P 1926 \tt T5:P1926 T5P1926

前面是精卫填海,具体怎么写可以看这个往后翻一点,前几篇写的不太好

这题先处理表示每项作业需要的时间,进行精卫填海的操作,之后有两种做法:

  • 再做一遍01背包,维护每道刷的题的时间。
  • 用贪心的思想,只要这题时间够,那么就做,再把时间减去,计数器统计做了多少道。
    01背包做法:
#include<bits/stdc++.h>
using namespace std;
int n,m,k,r,w2[15],w1[15],val[15],dp[155],f[155];
int main(){
	cin>>n>>m>>k>>r;
	for(int i=1;i<=n;i++)
		cin>>w2[i];
	for(int i=1;i<=m;i++)
		cin>>w1[i];
	for(int i=1;i<=m;i++)
		cin>>val[i];
	for(int i=1;i<=m;i++)
		for(int j=r;j>=w1[i];j--)
			dp[j]=max(dp[j],dp[j-w1[i]]+val[i]);
	for(int i=0;i<=r;i++)
		if(dp[i]>=k){
			r-=i;
			break;
		}
	for(int i=1;i<=n;i++)
		for(int j=r;j>=w2[i];j--)
			f[j]=max(f[j],f[j-w2[i]]+1);
	cout<<f[r];
	return 0;
}

贪心做法:

	sort(pro+1,pro+1+n);
	for(int i=1;i<=n;i++)
	{
		if(now>=pro[i])
		{
			now-=pro[i];
			ans++;
		}
	}
	cout<<ans;

总结

2 , 5 2,5 2,5 都是细节错误, 4 4 4 题思路错误, 3 3 3 题真的不会正解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值