终终终终于写关于dp的思考和总结了(因为之前真的没有理解和搞懂)
ok那接下来就进入正题吧 什么是背包问题?
P1048 [NOIP2005 普及组] 采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://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)https://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;
}