动态规划基本思想
- 确定状态变量(函数)
- 确定状态转移方程(递推关系)
- 确定边界
各种背包问题内容、思想、例题和代码实现
基础 0-1背包问题
0-1背包问题的基础思想
0-1背包详细题解
0-1背包简化/降维思想 – 有点冗杂,不过讲的很详细,主要就是:1. 逆序,因为本层得到的信息是由上一层递推得到的,所以逆序保护上一层信息,以保证在本层得到的信息的正确性;2. 降维的根本在于信息在递推过程中可被覆盖,不会影响结果,因为i层信息只与i-1层有关,与i-2层无关。
题目:最基础的0-1背包问题
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int wei[N],val[N];
//int f[N][N];//f[i][j]表示i件物品放入容量为j的背包的最大价值
int f[N];
int n,wemax;
int main()
{
cin>>n>>wemax;//4 5
for(int i=1;i<=n;i++)
{
cin>>wei[i]>>val[i];
}
/*未简化:
for(int i=1;i<=n;i++)
{
for(int j=0;j<=wemax;j++)//j是容量 对容量的[0,wemax]每个情况都考虑放与不放,保留较大值
{
f[i][j]=f[i-1][j];//不放第i个物品的值
if(j>=wei[i]) f[i][j]=max(f[i][j],f[i-1][j-wei[i]]+val[i]);
/* 解释:
j>=wei[i] 背包本身容量足够放入第i个物品
比较不放第i个物品价值较大还是放比较大(放的话可能之前的一些物品要取出)
f[i][j],即 f[i-1][j]为不放时的价值
f[i-1][j-wei[i]]+val[i]为 容量为j-wei[i]时的最大价值+当前物品的价值
即此时向之前的情况取价值最大且能放下第i个物品的情况
抽象的问题在于为什么是f[i-1]就行
因为根据定义 f[i-1]也就是其情况下的最优解了,最优解+v必然是f[i]下的最优
}
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<=wemax;j++)
{
cout<<f[i][j]<<" ";
}
cout<<endl;
}
cout<<f[n][wemax]<<endl;
*/
//简化:
for(int i=1;i<=n;i++)
{
//逆序
//第因为i层的f[j]的确定一定要用到第i-1层的f[j]不能用本层已经得到的f[j]所以一定要用逆序。
//一维的存储在于覆盖之前的信息即可,减少空间
for(int j=wemax;j>=wei[i];j--)
{
f[j]=max(f[j],f[j-wei[i]]+val[i]);
//cout<<f[j]<<" ";
}
//cout<<endl;
}
/*for(int i=1;i<=n;i++)
{
cout<<f[i]<<" ";
}*/
cout<<f[wemax]<<endl;
return 0;
}
/*
未简化:
f[i][j]:
j:1 2 3 4 5
0 2 2 2 2 2
0 2 4 6 6 6
0 2 4 6 6 8
0 2 4 6 6 8
简化:
f[i]: 2 4 6 6 8
循环内变化:
5 4 3 2 1 <-顺序
2 2 2 2 2
6 6 6 4
8 6 6
8 6
*/
有两个限制条件的0-1背包问题 – NASA的食物计划
题目:NASA的食物计划
#include<bits/stdc++.h>
using namespace std;
const int N=1e3;
int wei[N],v[N],ca[N];
int f[N][N];
struct node{
int v;
int w;
int ca;
}food[N];
int main()
{
int maxv,maxw;
cin>>maxv>>maxw;
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>food[i].v>>food[i].w>>food[i].ca;
}
for(int i=1;i<=n;i++)
{
for(int j=maxv;j>=food[i].v;j--)//第一重限制
{
for(int k=maxw;k>=food[i].w;k--)//第二重限制
{
f[j][k]=max(f[j][k],f[j-food[i].v][k-food[i].w]+food[i].ca);
}
}
}
cout<<f[maxv][maxw];
return 0;
}
物品可无限次被选择的完全背包问题
题目:完全背包问题,即再0-1背包基础上多一层循环,详细文字题解
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int f[N];
int wei[N],val[N];
/*
将选k个物品与wei[i]结合 f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i])
优化:
f[i , j ] = max( f[i-1,j] , f[i-1,j-wei]+val , f[i-1,j-2*wei]+2*val , f[i-1,j-3*wei]+3*val , .....)
f[i , j-v]= max( f[i-1,j-wei] , f[i-1,j-2*wei] + val , f[i-1,j-3*wei]+2*val , .....)
-------->则 f[i][j]=max(f[i-1][j] , f[i,j-wei]+val) ;
对比0-1背包 f[i][j]=max(f[i-1][j] , f[i-1,j-wei]+val);
在进一步优化前 f[i][j]=f[i-1][j];
进一步优化后:0-1背包为 f[i-1,j-wei]+val,则需要逆序,因为和i-1层有关,范围为[m, wei[i]]
但是完全背包为 f[i,j-wei]+val),不用逆序,从小到大即可,范围为[wei[i], m]
-------->状态转移方程:f[j] = max(f[j],f[j-wei[i]]+val[i])
*/
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>wei[i]>>val[i];
}
for(int i=1;i<=n;i++)
{
for(int j=wei[i];j<=m;j++)//从小到大枚举 因为i层和i-1层无关
{
f[j]=max(f[j],f[j-wei[i]]+val[i]);
}
}
cout<<f[m];
}
物体可有限次被选择的多重背包问题1
题目:多重背包问题1
在完全背包问题基础上加一个k<=num[i]&&k*wei[i]<=j
的限制,限制物品被选择次数。
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int f[N][N];
int wei[N],val[N],num[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>wei[i]>>val[i]>>num[i];
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
for(int k=0;k<=num[i]&&k*wei[i]<=j;k++)
f[i][j]=max(f[i][j],f[i-1][j-wei[i]*k]+val[i]*k);
}
}
cout<<f[n][m];
return 0;
}
多重背包问题2,二进制优化方法
题目:多重背包问题2 、题目视频讲解
没有特别理解,有空再仔细听听视频讲解
#include<bits/stdc++.h>
using namespace std;
const int N=2010;
int f[N];
struct Good{
int wei,val;
};
int main()
{
int n,m;
cin>>n>>m;
vector<Good> goods;
for(int i=1;i<=n;i++)
{
int w,v,s;
cin>>w>>v>>s;
for(int k=1;k<=s;k*=2)
{
s-=k;
goods.push_back({w*k,v*k});
}
if(s>0) goods.push_back({w*s,v*s});
}
for(auto good:goods)
{
for(int j=m;j>=good.wei;j--)
{
f[j]=max(f[j],f[j-good.wei]+good.val);
}
}
cout<<f[m];
return 0;
}
分组背包问题
题目:分组背包问题
题解:详细文字题解、视频题解
据说是多重背包问题的一般情况
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int f[N];
int wei[N][N],val[N][N],s[N];//s代表第i组物品个数
/*
0-1:前i个物品,体积小于j的最大价值
分组背包:前i组物品,体积小于j的最大价值
*/
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>s[i];
for(int j=0;j<s[i];j++)
{
cin>>wei[i][j]>>val[i][j];
}
}
for(int i=1;i<=n;i++)//阶段
{
for(int j=m;j>=0;j--)//i、j共同构成状态
{
for(int k=0;k<s[i];k++)//k为决策
{
if(wei[i][k]<=j)
{
f[j]=max(f[j],f[j-wei[i][k]]+val[i][k]);
}
}
}
}
cout<<f[m]<<endl;
return 0;
}
0-1背包扩展题 – 摆花
题目:摆花
解析:洛谷详解
思路:
这道题没有特别懂,后续再来考虑补充
未优化代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=105, mod = 1000007;
int n, m, a[maxn], f[maxn][maxn];
int main()
{
cin>>n>>m;
for(int i=1; i<=n; i++) cin>>a[i];
f[0][0] = 1;
for(int i=1; i<=n; i++)
for(int j=0; j<=m; j++)
for(int k=0; k<=min(j, a[i]); k++)
f[i][j] = (f[i][j] + f[i-1][j-k])%mod;
cout<<f[n][m]<<endl;
return 0;
}
优化版代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int f[N];
int a[N];
int mod=1000007;
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
f[0]=1;
for(int i=1;i<=n;i++)
{
for(int j=m;j>=0;j--)
{
for(int k=1;k<=min(a[i],j);k++)
{
f[j]=(f[j]+f[j-k])%mod;
}
}
}
cout<<f[m]<<endl;
}
更多背包问题汇总讲解文章补充:
文章1,除本文所含内容外,还有混合背包问题,即每个物品可被选择次数不同,和“摆花”有点像,即限定每种物品被选择次数,但每种物品选择次数都不一样
总结
在动态规划入门情况下,写了好几道和背包有关的题目,但是还是一知半解,没有独立分析和解决新动态规划问题的能力,只能根据记忆去回忆已经做过的题目和使用方法,再考虑如何解决新题目。希望后面题目越做越多能让情况不要这么局促吧…