说到DP想必大家都非常头疼,今天就来说说dp的入门问题背包DP(dp最简单一类)。通过背包dp,你能理解动态规划中最经典的状态转移的思想,学好背包绝对会为征服DP迈下最坚实的一步。好话不多说,我们来讲讲第一个问题。(如果想检验自己的代码请移步至acwing)
01背包
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
咱们先上代码再说
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010;
int f[N];
int main(){
//freopen("data.in","r",stdin);
//freopen("data.out","w",stdout);
int n,v;
cin>>n>>v;
for(int i=0;i<n;i++)
{
int c,w;
cin>>c>>w;
for(int j=v;j>=c;j--) f[j]=max(f[j],f[j-c]+w);
}
cout<<f[v]<<endl;
return 0;
}
我们可以看到代码非常的简洁,我们遍历每一次的决策,对于第i个物品,是选还是不选,保存最大的方案继续向下进行,我们可以看到我们是从最大体积到最小体积进行枚举的,这是为什么呢?(我实在解释不清,移步至DD大牛),咱们可以用模拟法,把每个状态打印下来就好理解了(我是这样理解的)
完全背包
有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
先上代码
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010;
int f[N];
int main(){
//freopen("data.in","r",stdin);
//freopen("data.out","w",stdout);
int n,v;
cin>>n>>v;
for(int i=0;i<n;i++)
{
int c,w;
cin>>c>>w;
for(int j=c;j<=v;j++) f[j]=max(f[j],f[j-c]+w);
}
cout<<f[v]<<endl;
return 0;
}
依然是用穷举法理解,我们可以看到完全背包与01背包在代码上的唯一区别就是枚举从从大到小改为从小到大。
多重背包(二进制优化)
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
直接上代码
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010;
int f[N];
int main(){
//freopen("data.in","r",stdin);
//freopen("data.out","w",stdout);
int n,v;
cin>>n>>v;
for(int i=0;i<n;i++)
{
int c,w,s;
cin>>c>>w>>s;
for(int j=1;j<=s;j<<=1)
{
s-=j;
for(int k=v;k>=j*c;k--)
f[k]=max(f[k],f[k-j*c]+j*w);
}
if(s)
for(int k=v;k>=s*c;k--)
f[k]=max(f[k],f[k-s*c]+s*w);
}
cout<<f[v]<<endl;
return 0;
}
这题我们重点讲解,对于多重背包有个很暴力的想法,我们将所有情况转化为01背包,直接求解,这就启发了我们用二进制优化,打个比方把:
比如说我们的s=10 ,10个体积为c,重量为w的物体我们能将其优化成4个物体:
1.(c,w)
2.(2c,2w)
3.(4c,4w)
4.(3c,3w)
只要你懂一点二进制,用1、2、3我们可以完美的表示7个体积为c,w的物体,再补上一个4即可。如果你没有学过二进制,请回去补一补请一头撞死 ,如果学过二级制再好好想一想一定能想出来的,如果想不出来,就再想一想请一头撞死 。
最后我想给大家分享一下多重背包的极限优化(单调队列优化),他能把时间复杂度优化为线性的。具体的做法和单调队列做法一样,将超过生命周期的数出队,以及将不满足单调性的队列中的元素剔除掉像极了现再的我,比我小的人比我NB咱们给出代码就不细讲了。因为正常题目二进制优化就够了,只要出题人有一点良知~~
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=20020;
int q[N];
int dp[N],g[N];
int tt,hh;
int main(){
int n,v;
cin>>n>>v;
for(int i=0;i<n;i++)
{
int c,w,s;
cin>>c>>w>>s;
memcpy(g,dp,sizeof g);
for(int j=0;j<c;j++)
{
tt=-1,hh=0;
for(int k=j;k<=v;k+=c)
{
while(tt>=hh&&k-q[hh]>s*c) hh++;
if(tt>=hh) dp[k]=max(dp[k],g[q[hh]]+(k-q[hh])/c*w);
while(tt>=hh&&g[k]-(k-j)/c*w>g[q[tt]]-(q[tt]-j)/c*w) tt--;
q[++tt]=k;
}
}
}
cout<<dp[v]<<endl;
return 0;
}