dp总结(仅对于01背包和完全背包进行总结)

终终终终于写关于dp的思考和总结了(因为之前真的没有理解和搞懂)

ok那接下来就进入正题吧 什么是背包问题?

P1048 [NOIP2005 普及组] 采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1048这个题目就是一个基本的01背包的问题

题目意思我就不讲了  我们应该怎么做呢?

假设我们的实验数据为:

背包的总体积为7

第一株药的采摘时间为4  价值为5

第二株药的采摘时间为3 价值为1

第三株药的采摘时间为2 价值为4

我们一眼看就可以看出来 最大的价值为5+4=9 那怎么把这种题目转化为背包问题呢?

我们可以把采每个药分离为一个个体

然后我们第一次采药完成之后我们的背包情况是这样的

因为小于4的背包部分都事不能将这个草药给装下的 因此在4之后的数字才会有数字

接下来我们要采第二株草药了

我们如何判断这个药是否该采?

要么是没采之前的状态 要么是采了这个 然后加上剩余空间还能放下的草药价值 两者去最大值

采完发现最大价值为6

采第三个草药的时候也是如此

 具体代码如下:

#include<bits/stdc++.h>

const int N=2e5+5;
using namespace std;
int dp[2005][2005];
int n,m,k,x,y,z,a[N];
inline void run(){
	cin>>m>>n;
	for(int i=1;i<=n;i++){
		cin>>x>>y;//x体积,y价值
		for(int j=1;j<=m;j++){ //枚举每个体积的框 
		//如果 j>=x 表示此时框的体积能够接受新的草药的体积 
			if(j>=x) dp[i][j]=max(dp[i-1][j],dp[i-1][j-x]+y);
			//dp[i-1][j]代表上一株草药 
			//dp[i-1][j-x]代表加上这个草药体积之后 剩下体积能够装的草药它的价值 两者之间进行比较
			//到底是我加了之后草药的价值高 还是不加的草药的价值高 取两者最大值 
			else dp[i][j]=dp[i-1][j];//如果装不下 只能继承上一个的框 
			
		} 
	}	cout<<dp[n][m]<<'\n'; 
}
int main(){
	run();
	return 0;
}

我们还可以将这个代码进行优化

dp[2005][2005]是放每一种采药的时候的情况 但是我们用的只需要之前一种采药 将这次采药的情况与前一种进行比较 

因此我们只需用dp[2][2005]即可完成任务

将奇数次放在dp[1][2005] 将偶数次放在dp[0][2005]

具体代码:

#include<bits/stdc++.h>

const int N=2e5+5;
using namespace std;
int dp[2][2005];
int n,m,k,x,y,z,a[N];
inline void run(){
	cin>>m>>n;
	for(int i=1;i<=n;i++){
		cin>>x>>y;//x体积,y价值
		for(int j=1;j<=m;j++){ //枚举每个体积的框 
		//如果 j>=x 表示此时框的体积能够接受新的草药的体积 
			if(j>=x) dp[i&1][j]=max(dp[(i-1)&1][j],dp[(i-1)&1][j-x]+y);
			//dp[i-1][j]代表上一株草药 
			//dp[i-1][j-x]代表加上这个草药体积之后 剩下体积能够装的草药它的价值 两者之间进行比较
			//到底是我加了之后草药的价值高 还是不加的草药的价值高 取两者最大值 
			else dp[i&1][j]=dp[(i-1)&1][j];//如果装不下 只能继承上一个的框 
		} 
	}	cout<<dp[n&1][m]<<'\n'; 
}
//优化: 因为我们每次用到的只是一个历史版本 因此我们的dp[2005][2005]不用开这么大
// 只要开dp[2][2005] 即可 将偶数弄到dp[0][j] 奇数弄到dp[1][j] 


int main(){
	run();
	return 0;
}

在这个代码的基础下 我们还能进行优化 我们可以用一维dp就可以完成任务

只不过在内循环的时候我们不能从头开始 而是要从尾巴开始 

因为从头开始更新值之后会影响到后面的值的更新 从头开始是完全背包

所以 对于01背包最简代码如下:

#include<bits/stdc++.h>

const int N=2e5+5;
using namespace std;
int dp[10000005];
int n,m,k,x,y,z,a[N];
inline void run(){
	cin>>m>>n;
	for(int i=1;i<=n;i++){
		cin>>x>>y;//x体积,y价值
		for(int j=m;j>=x;j--){ 
			dp[j]=max(dp[j-x]+y,dp[j]);
		}
	}
	cout<<dp[m]<<'\n';
}

int main(){
	run();
	return 0;
}

对于完全背包,我们的最简代码为:

#include<bits/stdc++.h>

const int N=2e5+5;
using namespace std;
int dp[10000005];
int n,m,k,x,y,z,a[N];
inline void run(){
	cin>>m>>n;//m代表背包体积 n代表有几个药要去采 
	for(int i=1;i<=n;i++){
		cin>>x>>y;//x体积,y价值
		for(int j=x;j<=m;j++){ 
			dp[j]=max(dp[j-x]+y,dp[j]);
		}
	}
	cout<<dp[m]<<'\n';
}

int main(){
	run();
	return 0;
}

                        P1455 搭配购买 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1455                                                                   

这是一个背包和并查集联通的一个题目

把有关联的云朵并成一个集合 然后算出这个集合的总价值 最后在换成01背包进行完成

#include<bits/stdc++.h>

const int N=2e5+5;
using namespace std;
long long dp[N];
int n,m,k,x,y,z,l,w,a[N],b[N];
int pre[N];
int size[N];//代表我们这个组织的大小 
int find(int x){
	if(pre[x]==x)return x ;
	else return find(pre[x]);
}
void join(int x,int y){
	int fx=find(x);
	int fy=find(y);
	if(fx!=fy){
		pre[fx]=fy;
		a[fy]+=a[fx];
		b[fy]+=b[fx];//现在的老大加原来的老大 
	}
}

inline void run(){
	cin>>n>>m>>w;
	//n代表有n朵云,m代表有m个搭配 w代表我拥有的钱的大小 
	for(int i=1;i<=n;i++){
		cin>>x>>y;
		//x代表要的钱 y代表它的价值 
		//a[i]表示第i个的云的钱为x
		//b[i]表示第i个的云的价值为y 
		a[i]=x;
		b[i]=y;
		pre[i]=i;
	}
	//将两朵云进行合并 
	for(int i=1;i<=m;i++){
		cin>>x>>y;
		if(find(x)!=find(y))join(x,y);
	}
	for(int i=1;i<=n;i++){
		if(pre[i]==i){
			for(int j=w;j>=a[i];j--){
				dp[j]=max(dp[j],dp[j-a[i]]+b[i]);
			}
		}
	}
	cout<<dp[w]<<'\n';
}
int main(){
	run();
	return 0;
}

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值