WEEK_5(背包)

P1048 采药

灵魂发问:如果你是辰辰,你能完成这个任务吗?

答案是:还是不太难的

这一题很基础一个01背包,属于纯纯板子题,输入输出弄好了上直接状态转移方程即可,下面是优化成一维数组的代码

就是要注意:在这里需要 j 反着遍历,不然就有可能会有重复的情况,反着来的话,前面的情况都是0,就不会出现一个物品被选两次的情况啦

代码如下:

#include<bits/stdc++.h>
using namespace std;
int m,t;
const int N=1e3+5;
int dp[N];
int ti[N],va[N];
int main()
{
	cin>>t>>m;
	for(int i=1;i<=m;i++)
	cin>>ti[i]>>va[i];
	for(int i=m;i>=1;i--)
	{
		for(int j=t;j>=ti[i];j--)
		dp[j]=max(dp[j],dp[j-ti[i]]+va[i]);
	}
	cout<<dp[t];
	return 0;
}

P1616 疯狂的采药

对于这一题只能说大无语,是个完全背包的板子题啦

如果你是 LiYuxiang,你能完成这个任务吗?

也可以捏

和01背包有所不同的是,完全背包需要从头开始,就不能反着遍历了,然后记得开long long,不然一切白搭,然后。就没有然后了,跟01一模一样了

代码如下:

#include<bits/stdc++.h>
using namespace std;
long long m,t;
const int N=1e4+5,M=1e7+5;
long long dp[M];
long long ti[N],va[N];
int main()
{
	cin>>t>>m;
	for(int i=1;i<=m;i++)
	cin>>ti[i]>>va[i];
	for(int i=1;i<=m;i++)
	{
		for(int j=ti[i];j<=t;j++)
		dp[j]=max(dp[j],dp[j-ti[i]]+va[i]);
	}
	cout<<dp[t];
	return 0;
}

P1049 装箱问题

01背包,不多说了

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=2e4+5,M=35;
int dp[M][N];
int main()
{
	int v,n;
	memset(dp,0,sizeof(dp));
	cin>>v>>n;
	int vv[M];
	for(int i=1;i<=n;i++)
	cin>>vv[i];
	for(int i=1;i<=n;i++)
	{
		for(int j=v;j>=0;j--)
		{
			if(j>=vv[i])
			dp[i][j]=max(dp[i-1][j],dp[i-1][j-vv[i]]+vv[i]);
			else
			dp[i][j]=dp[i-1][j]; 
		}
	}
	cout<<v-dp[n][v];
	return 0;
}

P1833 樱花

这题综合性挺强的,01背包,完全背包,和多重背包凑一块了,害,没事啦没事啦,两种思路

一种是全部转化成01背包:完全背包次数设为999999,再和多重背包一起进行二进制转化,最后都变成01背包模型

第二种是单单多重背包转化成01背包,完全背包在判断出来同时直接进行dp,单设数组存多重背包和01后,再对其进行一次dp,然后就ok啦

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=1e3+5;
int ti[N],co[N];
int dp[N]; 
int main()
{
	//t为看完第i棵树所需时间,c为价值,p为次数 
	int h,hh,m,mm,t,c,p,n;
	char a;
	int cnt=1;
	cin>>h>>a>>m>>hh>>a>>mm>>n;
	int tt=(hh-h)*60+mm-m;
	memset(dp,0,sizeof(dp));
	for(int i=1;i<=n;i++)
	{
		cin>>t>>c>>p;
		if(p==0)
		{
			for(int j=t;j<=tt;j++)
			dp[j]=max(dp[j],dp[j-t]+c);
		}
		int q=1;
		while(p>=q)
		{
			co[cnt]=q*c;
			ti[cnt++]=q*t;
			p-=q;
			q<<=1;
		}
		if(p)
		{
			co[cnt]=p*c;
			ti[cnt++]=p*t;
		}
	}
	for(int i=1;i<=cnt;i++)
	for(int j=tt;j>=ti[i];j--)
	{
		dp[j]=max(dp[j],dp[j-ti[i]]+co[i]);
	}
	cout<<dp[tt];
}

以下是选做题:

P1077 摆花

定义状态:dp[i][j]表示前 i 种花总和为 j 的方案数

可以看出来需要有三个循环,一个是遍历花种数 i,一个用来遍历从0到最大摆放数 j,最里面的用来遍历每种花的数量 k,可以推出:知道了第 i-1 种前的所有种类的花要摆放的数量,那么对于第 i 种花需要的数量就需要加上 i-1 种花的种类数,同时减去摆放的 k 盆花,那么可以得到如下状态转移方程:

f[i][j]+=f[i-1][j-k]

代码送上:

#include<bits/stdc++.h>
using namespace std;
int n,m;
int md=1e6+7;
int a[105];
int dp[105][105];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	cin>>a[i];
	dp[0][0]=1;
	for(int i=1;i<=n;i++)
	for(int j=0;j<=m;j++)
	for(int k=0;k<=min(a[i],j);k++)
	dp[i][j]=(dp[i][j]+dp[i-1][j-k])%md;
	cout<<dp[n][m];
}

P1064 金明的预算方案

救命,乍一看这题,以为就是个简简单单的背包,可是理解了题目意思后却发现,暗藏玄机啊。。。好好的物件,居然还分成了主件和附件,那么就有待考究了。

对于主件附件,直接想法是把主件和附件分类,可是还需要让每个附件对应上每个主件,并且想要购买附件就必须要买主件,不过好在附件只会有0,1,2个,那就好办啦,把分成4种情况:

1、只考虑主件

2、考虑主件以及每个主件的第一个附件

3、考虑主件以及每个主件的第二个附件

4、考虑主件以及所有的附件(最多两个)

因此,问题就转化成很板的背包问题啦

状态转移方程:

dp[j]=max(dp[j],dp[j-zjg[i]]+zzy[i]);
		
if(j>=zjg[i]+fjg[i][1])
dp[j]=max(dp[j],dp[j-zjg[i]-fjg[i][1]]+zzy[i]+fzy[i][1]);
		
if(j>=zjg[i]+fjg[i][2])
dp[j]=max(dp[j],dp[j-zjg[i]-fjg[i][2]]+zzy[i]+fzy[i][2]);

if(j>=zjg[i]+fjg[i][1]+fjg[i][2])
dp[j]=max(dp[j],dp[j-zjg[i]-fjg[i][1]-fjg[i][2]]+zzy[i]+fzy[i][1]+fzy[i][2]);

代码如下:

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=1e5+5;
int v,w,p,f=0;
int dp[N],zjg[N],zzy[N],fjg[N][3],fzy[N][3];
int maxx=0;
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>v>>w>>p;
		if(p==0)
		zjg[i]=v,zzy[i]=v*w;
		else
		fjg[p][0]++,fjg[p][fjg[p][0]]=v,fzy[p][fjg[p][0]]=v*w;
	}
	for(int i=1;i<=m;i++)
	for(int j=n;j>=zjg[i];j--)
	{
		dp[j]=max(dp[j],dp[j-zjg[i]]+zzy[i]);
		
		if(j>=zjg[i]+fjg[i][1])
		dp[j]=max(dp[j],dp[j-zjg[i]-fjg[i][1]]+zzy[i]+fzy[i][1]);
		
		if(j>=zjg[i]+fjg[i][2])
		dp[j]=max(dp[j],dp[j-zjg[i]-fjg[i][2]]+zzy[i]+fzy[i][2]);
		
		if(j>=zjg[i]+fjg[i][1]+fjg[i][2])
		dp[j]=max(dp[j],dp[j-zjg[i]-fjg[i][1]-fjg[i][2]]+zzy[i]+fzy[i][1]+fzy[i][2]);
	}
	cout<<dp[n];
	return 0;
}

P8742 砝码称重

这题真的想不出来一点,特别是它的加上一个砝码,后来又要减去一个砝码,特别难实现的感觉,不过这题又给我带来了一个新思路

将总重量逐渐减小,并遍历下去,分成四种情况:

1、目前重量是否等于某个砝码单独的重量;

2、不取第 i 个砝码,判断是否有过该重量;

3、取第 i 个砝码,加到同侧,判断拿 i 个砝码是否达到过此重量;

4、取第 i 个砝码,加到对侧,即目前重量减去砝码重的绝对值,判断拿 i 个砝码是否达到过此重量

如果有这些情况发生,那么对于dp就赋予1,意为拿 i 个砝码达到重量 j 的情况有过了,当然了,最后要求的是使用 n 个砝码,于是就有了最后一波遍历

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=105,M=1e8+5;
long long dp[101][100001];
long long pp[M];
int n;
int ans;
int w[N];
int main()
{
	cin>>n;
	int ww=0;
	for(int i=1;i<=n;i++)
	{
		cin>>w[i];
		ww+=w[i];
	}
	for(int i=1;i<=n;i++)
	for(int j=ww;j;j--)
	{
		if(j==w[i])
		dp[i][j]=1;
		else if(dp[i-1][j])
		dp[i][j]=1;
		else if(dp[i-1][j+w[i]])
		dp[i][j]=1;
		else if (dp[i-1][abs(j-w[i])])
		dp[i][j]=1;
	}
	for(int i=1;i<=ww;i++)
	if(dp[n][i])
	ans++;
	cout<<ans;
	return 0;
}

P1855 榨取kkksc03

本题属于多重背包的板子题,有时间和金钱两条限制,于是很容易能得到两个状态:时间剩余和金钱剩余,再根据01背包的模版,那么状态转移方程即为:

dp[j][k]=max(dp[j][k],dp[j-mm[i]][k-tt[i]]+1)

代码如下:

#include<bits/stdc++.h>
using namespace std;
int n,m,t;//n个愿望,剩余m元,剩余t分钟
const int N=205;
int dp[N][N]; 
int mm[N],tt[N];
int maxx;
int main()
{
	cin>>n>>m>>t;
	for(int i=1;i<=n;i++)
	cin>>mm[i]>>tt[i];
	for(int i=1;i<=n;i++)
	for(int j=m;j>=mm[i];j--)
	for(int k=t;k>=tt[i];k--)
	dp[j][k]=max(dp[j][k],dp[j-mm[i]][k-tt[i]]+1);
	
	cout<<dp[m][t];
	return 0;
}

P2946 Cow Frisbee Team S

这题属于是比较奇怪的一题了,从头到尾所有字无一不在呐喊:我是背包,可是问的问题却是要求种类数,很奇葩,无从下手

那么就换个角度,既然要达到F的倍数才算,那么就这样定义状态:

dp[i][j]表示选 i 头牛,各个数之和为 j 时,所有的方案数,那么就有几种情况:

1、已经选了 i 头牛,但是没有选第 i 头,应在前 i−1 头牛中取来若干和为 j 的数即dp[i-1][j]

2、选取第 i 头,则应在前 i−1 头牛中取来若干和为 j−r[i] 的数这样最后才是 j 头牛

但是要时刻记得mod!!

那么状态转移方程也就呼之欲出啦:

dp[i][j]=dp[i][j]+dp[i-1][j]+dp[i-1][j-r[i]]

(由于格式问题,此处没有%),那么代码如下:

#include<bits/stdc++.h>
using namespace std;
int n,f;
int md=1e8;
const int N=2e3+5;
int r[N];
int dp[N][N];
int main()
{
	cin>>n>>f;
	for(int i=1;i<=n;i++)
	{
		cin>>r[i];
		r[i]%=f;
	}
	for(int i=1;i<=n;i++)
	dp[i][r[i]]=1;
	for(int i=1;i<=n;i++)
	for(int j=0;j<f;j++)
	dp[i][j]=dp[i][j]%md+dp[i-1][j]%md+dp[i-1][(j-r[i]+f)%f]%md,dp[i][j]%=md;
	cout<<dp[n][0];
	return 0;
}

P5020 货币系统

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值