一直背包问题都是套模板,就没弄明白过,今天来理解理解~~~
首先就是最原始的二维的背包,未优化的劣势就是空间复杂度太大,数据大了装不下,但原始的好处是可以回溯看具体哪个物品放了,哪个没放,因为所有信息都记录下来了嘛:
其中
i f ( j < c ) d p [ i ] [ j ] = d p [ i − 1 ] [ j ] if(j<c)dp[i][j]=dp[i-1][j] if(j<c)dp[i][j]=dp[i−1][j]
这句话必须写,我之前就受空间优化的影响。。。没写这句话,,,在此感谢我滴队友 Lzj 童鞋~因为如果不写就有可能因为没转移到而变成0
#include"iostream"
#include"cstring"
using namespace std;
const int maxn=1e5+5;
int dp[110][maxn];
int pre,now;
int N,W;//N表示物品个数,W表示背包容量
int vis[110];
int C[110],V[110];
/*用来回溯,看哪些用了哪些没用*/
void traceback()
{
int w=W;
for(int i=N;i>1;i--)
{
if(dp[i][w]==dp[i-1][w])vis[i]=0;
else
{
vis[i]=1;
w-=C[i];
}
}
if(dp[1][w])vis[1]=1;
else vis[1]=0;
for(int i=1;i<=N;i++)cout<<vis[i]<<" ";
cout<<endl;
}
void print()
{
for(int i=1;i<=N;i++)
{
for(int j=1;j<=W;j++)cout<<dp[i][j]<<" ";
cout<<endl;
}
}
int main()
{
while(cin>>N>>W)
{
memset(dp,0,sizeof(dp));
pre=0;
for(int i=1;i<=N;i++)
{
int c,v;
cin>>c>>v;
C[i]=c,V[i]=v;
for(int j=1;j<=W;j++)
{
if(j<c)dp[i][j]=dp[i-1][j];/*都小于了,说明第i个装不下,直接不要*/
else dp[i][j]=max(dp[i-1][j],dp[i-1][j-c]+v);
}
}
cout<<dp[N][W]<<endl;
print();
traceback();
}
}
因为我们大多数时候要的是最后答案,而整个计算过程只与这一次和上一次有关,于是优化为滚动数组:
#include"iostream"
#include"cstring"
using namespace std;
const int maxn=1e5+5;
int dp[2][maxn];
int pre,now;
int N,W;//N表示物品个数,W表示背包容量
void bag01(int v,int c)//v表示价值,c表示重量
{
now=pre^1;
for(int j=1;j<=W;j++)
{
if(j<c)dp[now][j]=dp[pre][j];
else dp[now][j]=max(dp[pre][j],dp[pre][j-c]+v);
}
pre=now;
}
int main()
{
while(cin>>N>>W)
{
memset(dp,0,sizeof(dp));
pre=0;
for(int i=1;i<=N;i++)
{
int c,v;
cin>>c>>v;
bag01(v,c);
}
cout<<dp[now][W]<<endl;
}
}
接下来就是烧脑的时候了:
假设我们去掉第一维
我们假设算 d p [ 10 ] dp[10] dp[10]的时候有一个重量为3,价值为999的东西,那么写出来就是:
d p [ 10 ] = m a x ( d p [ 10 ] , d p [ 10 − 3 ] + 999 ) dp[10]=max(dp[10],dp[10-3]+999) dp[10]=max(dp[10],dp[10−3]+999)
即:
d p [ 10 ] = m a x ( d p [ 10 ] , d p [ 7 ] + 999 ) dp[10]=max(dp[10],dp[7]+999) dp[10]=max(dp[10],dp[7]+999)
而这里的 d p [ 7 ] dp[7] dp[7]要的是上一次的 d p [ 7 ] dp[7] dp[7]
相当于:
d p [ i ] [ 10 ] = m a x ( d p [ i ] [ 10 ] , d p [ i − 1 ] [ 7 ] + 999 ) dp[i][10]=max(dp[i][10],dp[i-1][7]+999) dp[i][10]=max(dp[i][10],dp[i−1][7]+999)
那么怎样保证是上一次的 d p [ 7 ] dp[7] dp[7]喃?
我以前就一直没懂为啥要倒着来遍历。。。
因为你正着来嘛,弄到 d p [ 10 ] dp[10] dp[10]的时候, d p [ 7 ] dp[7] dp[7]就已经更新过了,相当于已经是 d p [ i ] [ 7 ] dp[i][7] dp[i][7]了
转移方程就变成了: d p [ i ] [ 10 ] = m a x ( d p [ i ] [ 10 ] , d p [ i ] [ 7 ] + 999 ) dp[i][10]=max(dp[i][10],dp[i][7]+999) dp[i][10]=max(dp[i][10],dp[i][7]+999),这样就错了
而如果倒着来, d p [ 7 ] dp[7] dp[7]还没有更新过,就还是 d p [ i − 1 ] [ 7 ] dp[i-1][7] dp[i−1][7]
所以要倒着来
而且~~~~
遍历的时候直接只用便利到 c [ i ] c[i] c[i],因为小于 c [ i ] c[i] c[i]的时候是直接 d p [ i ] [ [ j ] = d p [ i − 1 ] [ j ] dp[i][[j]=dp[i-1][j] dp[i][[j]=dp[i−1][j]的,相当于上一次与这一次没区别,既然没区别就懒得再去遍历了,反正都是那么多了
而且写了貌似还要遭。。。因为要数组越界。。。。
这个就阔以算是最后版本了,以后当模板用~
#include"iostream"
#include"string.h"
#include"algorithm"
using namespace std;
const int maxn=1e4+5;
int dp[maxn];
int N,W;
void bag01(int c,int v)
{
for(int i=W;i>=c;i--)
{
dp[i]=max(dp[i],dp[i-c]+v);
}
}
int main()
{
while(cin>>N>>W)
{
for(int i=0;i<N;i++)
{
int c,v;
cin>>c>>v;
bag01(c,v);
}
cout<<dp[W]<<"\n";
}
}
一种是不要求背包装满,这个时候 d p dp dp要初始化为0
另一种就是,要求背包装满,只有 d p [ 0 ] = 0 dp[0]=0 dp[0]=0,其他 d p dp dp初始化为 − ∞ -\infty −∞
唉~不懂,听别的小朋友说,初始化的意思就是说,背包要属于“合法”状态,
比如 不要求背包装满: d p [ 0 ] = 0 dp[0]=0 dp[0]=0是阔以的嘛,我啥都不装是阔以滴嘛~
但是要求装满的时候 d p [ 100 ] = 0 dp[100]=0 dp[100]=0是啥意思?包包都是满的,那肯定是有装东西滴,那有装东西你还价值为0???
这样不是就矛盾了么~就处于一个 “未定义” 状态,所以要初始化为 − ∞ -\infty −∞
完全背包就是第i个物品有很多个,阔以拿0个,1个,2个。。。。。
所以1.0版本就不来了,直接来2.0版本的滚动数组:
#include"iostream"
#include"cstring"
using namespace std;
const int maxn=1e5+5;
int dp[2][maxn];
int pre,now;
int N,W;//N表示物品个数,W表示背包容量
void bagall(int v,int c)//v表示价值,c表示重量
{
now=pre^1;
for(int j=1;j<=W;j++)
{
for(int k=0;k*c<=j;k++)//这件物品取0个,1个,2个。。。。
{
//如果剩下的空间还能再放一个
if(c<=j)dp[now][j]=max(dp[pre][j],dp[pre][j-k*c]+k*v);
//再多放一个都不得行
else dp[now][j]=dp[pre][j];
}
}
pre=now;
}
int main()
{
while(cin>>N>>W)
{
memset(dp,0,sizeof(dp));
pre=0;
for(int i=1;i<=N;i++)
{
int c,v;
cin>>c>>v;
bagall(v,c);
}
cout<<dp[now][W]<<endl;
}
}
然后烧脑的3.0版本来了~
因为第 i i i个物品有很多个,那我就考虑我当前这个情况再多放一个要不要得~
那写出来就是
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − c ] + v ) dp[i][j]=max(dp[i-1][j],dp[i][j-c]+v) dp[i][j]=max(dp[i−1][j],dp[i][j−c]+v)
要不得我就继承 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i−1][j]的
要得的话我就多放一个 d p [ i ] [ j − c ] + v dp[i][j-c]+v dp[i][j−c]+v
那这个样子就好理解为啥完全背包要正着来了嘛,我要的就是更新之后的,
算 d p [ 10 ] dp[10] dp[10]的时候,要的就是更新后的 d p [ 7 ] dp[7] dp[7],so~
#include"iostream"
#include"cstring"
using namespace std;
const int maxn=1e5+5;
int dp[2][maxn];
int pre,now;
int N,W;//N表示物品个数,W表示背包容量
void bagall(int v,int c)//v表示价值,c表示重量
{
for(int j=c;j<=W;j++)
{
dp[j]=max(dp[j],dp[j-c]+v);
}
}
int main()
{
while(cin>>N>>W)
{
memset(dp,0,sizeof(dp));
pre=0;
for(int i=1;i<=N;i++)
{
int c,v;
cin>>c>>v;
bagall(v,c);
}
cout<<dp[now][W]<<endl;
}
}
01背包 和完全背包 理解了那么 多重背包 就好理解了~
因为就算用的这两个~
多重背包就是第 i i i 种物品有 n i n_i ni 个。
##① 多重背包(1.0)
那么我们阔以把这 n i n_i ni 个物品分别都 01背包 一哈,这个(1.0)版本的太暴力了,就不写了,直接来2.0版本~
##② 多重背包(2.0)
2.0 2.0 2.0版本就是我们把 n i n_i ni 拆成一坨一坨的,拆成 2 0 , 2 1 , 2 2 , 2 3 . . . . . 2^0,2^1,2^2,2^3..... 20,21,22,23.....就像快速幂那样,这样就算 n i n_i ni 很大,也只有对数级别的几个~
2 64 2^{64} 264个这种物品也只有 64 64 64 坨~
然后剩下不够 2 的多少次方的又看成一坨就行了
#include"iostream"
#include"string.h"
#include"algorithm"
using namespace std;
const int maxn=1e5+5;
int dp[maxn];
int N,W;
void bag01(int c,int v)
{
for(int i=W;i>=c;i--)
{
dp[i]=max(dp[i],dp[i-c]+v);
}
}
void bagall(int c,int v)
{
for(int i=c;i<=W;i++)
{
dp[i]=max(dp[i],dp[i-c]+v);
}
}
void bagmul(int c,int v,int n)
{
//如果背包装不完所有的这种物品,那相当于就算无穷多个嘛,无穷多个就装不完嘛~
if(n*c>W)
{
bagall(c,v);
return;
}
int k=1;
while(n)
{
if(n<k)break;
bag01(c*k,v*k);
n-=k;
k<<=1;
}
//剩下的又分成一坨~
bag01(c*n,v*n);
}
int main()
{
while(cin>>N>>W)
{
for(int i=0;i<N;i++)
{
int c,v,n;
cin>>c>>v>>n;
bagmul(c,v,n);
}
cout<<dp[W]<<"\n";
}
}
用单调队列优化。。。我不会。。。。以后再说。。。。。
最后一个阔以作为模板了~
要是出现背包问题的裸题,那么就阔以把他秒了~
大01背包问题就是背包容量特别大,普通的01背包无论是时间上还是空间上都会炸掉
但是像这一类题目会发现: 价 值 ∗ 物 品 个 数 价值*物品个数 价值∗物品个数 很小
也正因为这样才阔以dp,只是 m a x max max变成了 m i n min min
d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i个物品中,价值为 j j j的所需要的最小的容量
不理解的地方:照理说 d p dp dp表示的是最小值,但是为什么初值还是 d p [ p r e ] [ 0 ] = 0 dp[pre][0]=0 dp[pre][0]=0而不是弄成 ∞ \infty ∞
还是 2.0 2.0 2.0版本的滚动数组
#include"iostream"
#include"cstring"
using namespace std;
const int maxn=1e4+5;
int dp[2][maxn];
int N,W;//W是背包容量
int CC,VV;
int now,pre;
int res;
void Bigbag01(int v,int c)
{
now=pre^1;
for(int i=0;i<=VV;i++)
{
if(i>=v)dp[now][i]=min(dp[pre][i],dp[pre][i-v]+c);
else dp[now][i]=dp[pre][i];
if(dp[now][i]<=W)res=max(res,i);//在这里找最大的结果
}
pre=now;
}
int main()
{
int T;
cin>>T;
while(T--)
{
memset(dp,0x3f,sizeof(dp));
res=-1;
pre=0;
cin>>N>>W;
int v[maxn],c[maxn];
CC=0,VV=0;
for(int i=1;i<=N;i++)
{
cin>>c[i]>>v[i];
CC+=c[i],VV+=v[i];
}
dp[pre][0]=0;
for(int i=1;i<=N;i++)
{
Bigbag01(v[i],c[i]);
}
cout<<res<<endl;
}
}
#include"iostream"
#include"cstring"
using namespace std;
const int maxn=1e4+5;
int dp[maxn];
int N,W;//W是背包容量
int CC,VV,res;
void Bigbag01(int v,int c)
{
for(int i=VV;i>=v;i--)
{
dp[i]=min(dp[i],dp[i-v]+c);
if(dp[i]<=W)res=max(res,i);
}
}
int main()
{
int T;
cin>>T;
while(T--)
{
res=-1;
memset(dp,0x3f,sizeof(dp));
cin>>N>>W;
int v[maxn],c[maxn];
CC=0,VV=0;
for(int i=1;i<=N;i++)
{
cin>>c[i]>>v[i];
CC+=c[i],VV+=v[i];
}
dp[0]=0;
for(int i=1;i<=N;i++)
{
Bigbag01(v[i],c[i]);
}
cout<<res<<endl;
}
}
1.poj2184 以及 power oj 2793(可看数据)
2.大01背包裸题