这里写了做者学过的一些背包问题的解法,但愿能为新入门DP的OIer提供便利。php
\(\LaTeX\) 懒得修了,凑合着看吧QwQ~c++
2019/11/25:更新了混合背包。
2020/09/24:更新了多重背包的二进制优化和加二进制优化后的混合背包~数组
01背包解决的就是有一堆东西,有体积量和价值,要放到一个容量为m的背包里,使得价值之和最大。
重点: 每一个物品只有1个。
之因此叫01背包,由于没个物品就是取或不取,取是1,不取是0。优化
思路: 设\(dp_{i,j}\)表示前i个物品放到容量为j的背包中的最大价值,则
\(dp_{i,j}=max(dp_{i-1,j},dp_{i-1,j-w_i}+c_i)\)
\(dp_{i-1,j}\)表示不取这个东西,那么容量仍是\(j\)。\(dp_{i-1,j-w_i}+c_i\)表示取,那么以前的容量就是\(j-w_i\)。spa
优化: 咱们发现,咱们只须要\(dp_{i-1}\),而不须要更前面的数据,因此能够换成两个数组的滚动数组,而后,咱们发现,只须要i以前的数保留便可,那么能够从后往前赋值,这样只要一个数组就能完成。code
代码:ci
#include<bits/stdc++.h> using namespace std; int n,m; //n是物品数,m是背包容量 int w[505],c[505]; //wi表示第i个物品的重量,ci表示价值 int dp[6005]; int main() { cin>>n>>m; for(int i=1;i<=n;i++) cin>>w[i]>>c[i]; for(int i=1;i<=n;i++)//枚举每一个物品 for(int j=m;j>=w[i];j--)//枚举背包容量 dp[j]=max(dp[j],dp[j-w[i]]+c[i]); //状态转移方程 cout<<dp[m]; return 0; }
彻底背包,就是一个物品能取无限次,求最大价值。get
咱们写01背包时之因此要从后往前,是要避免重复取一个东西,而彻底背包就是一个物品能取无限次,因此只要把内层循环改为\(w_i\)~\(m\)便可。it
代码:io
#include<bits/stdc++.h> using namespace std; int n,m; //n是物品数,m是背包容量 int w[505],c[505]; //wi表示第i个物品的重量,ci表示价值 int dp[6005]; int main() { cin>>n>>m; for(int i=1;i<=n;i++) cin>>w[i]>>c[i]; for(int i=1;i<=n;i++)//枚举每一个物品 for(int j=w[i];j<=m;j++)//枚举背包容量 dp[j]=max(dp[j],dp[j-w[i]]+c[i]); //状态转移方程 cout<<dp[m]; return 0; }
多重背包,第i个物品能取\(0\)~\(s_i\)个,求最大价值。
咱们能够把多重背包的一个物品取屡次当作多个同样的物品,例如,1号物品有2个,咱们能够当作1和1
。而后作01背包便可。
代码:
#include<bits/stdc++.h> using namespace std; int n,m; //n是物品数,m是背包容量 int w[505],c[505],s[505]; //wi表示第i个物品的重量,ci表示价值,si表示数量 int dp[6005]; int main() { cin>>n>>m; for(int i=1;i<=n;i++) cin>>w[i]>>c[i]>>s[i]; for(int k=1;k<=n;k++)//枚举物品种类 for(int i=1;i<=s[k];i++) //枚举这个种类的物品的个数 for(int j=m;j>=w[k];j--)//枚举背包容量 dp[j]=max(dp[j],dp[j-w[k]]+c[k]); //状态转移方程 cout<<dp[m]; return 0; }
在一些状况中,咱们若是把多重背包当01背包来处理,数量太多了,这时咱们就须要二进制优化。
二进制优化就是把多个同样的物品分为1个一组,2个一组,4个一组……n个一组,再把剩下来的搞成一组。
咱们都知道咱们能够用2的\(1\) ~ \(n\)次幂表示\(1\) ~ \(2^{n+1}-1\)的数,因此这样作是可行的。
例题
代码:
#include<cmath> #include<cstdio> #include<algorithm> #define rg register using namespace std; int T,n,m,mx,cnt,tmp; int a[2005],v[2005]; int t[500005]; bool dp[500005]; inline int read() { int x=0;int f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();} return x*f; } int main() { n=read(),m=read(); for(rg int i=1;i<=n;i++) { int aa,vv; aa=read(),vv=read(); for(rg int j=1;vv-j>=0;j*=2) { a[++cnt]=j*aa; vv-=j; } if(vv>0) a[++cnt]=vv*aa; } for(rg int i=1;i<=m;i++) { t[i]=read(); if(t[i]>mx) mx=t[i]; } dp[0]=true; for(rg int i=1;i<=cnt;i++) { while(dp[tmp]&&tmp<=mx) tmp++; if(tmp>=mx) break; int nd=max(tmp,a[i]); for(rg int j=mx;j>=nd;j--) if(dp[j-a[i]]) dp[j]=true; } for(rg int i=1;i<=m;i++) if(dp[t[i]]) printf("Yes\n"); else printf("No\n"); return 0; }
分组背包,就是把东西分红t组,每组最多取1个,最少不取,求最大价值。
咱们能够把每组当作同样物品,只不过它的体积和价值是会变的,咱们只要像作01背包那样,最后再循环判断每组里的物品就好了。
代码:
#include<bits/stdc++.h> using namespace std; int n,m,t; //n是物品数,m是背包容量,t是组数 int a[15][505]; //aij表示第i组的第j个物品的编号 int w[505],c[505]; //wi表示第i个物品的重量,ci表示价值 int dp[6005]; int main() { cin>>m>>n>>t; for(int i=1;i<=n;i++) { int p; cin>>w[i]>>c[i]>>p; a[p][++a[p][0]]=i;//存储 } for(int i=1;i<=t;i++)//枚举每组物品 for(int j=m;j>=0;j--)//枚举背包容量 for(int k=1;k<=a[i][0];k++) //枚举每组中的每一个物品 if(j>=w[a[i][k]])//判断是否能够放下这个东西 dp[j]=max(dp[j],dp[j-w[a[i][k]]]+c[a[i][k]]); //状态转移方程 cout<<dp[m]; return 0; }
固然混合背包中的多重背包也能用二进制优化啦~
例题
代码:
#include<cstdio> using namespace std; int n,m,t; //n是物品数,m是背包容量 int w[100005],c[100005]; //wi表示第i个物品的重量,ci表示价值 bool p[100005]; //pi表示状态 int dp[1005]; int tsh,tsm,teh,tem; int max(int x,int y){return x>y?x:y;} int main() { scanf("%d:%d %d:%d %d",&tsh,&tsm,&teh,&tem,&n); tsm+=tsh*60,tem+=teh*60; m=tem-tsm; for(int i=1;i<=n;i++) { int ww,cc,pp; scanf("%d%d%d",&ww,&cc,&pp); if(pp) { for(int j=1;pp-j>=0;j*=2) { t++; w[t]=j*ww; c[t]=j*cc; p[t]=true; pp-=j; } if(pp) { t++; w[t]=pp*ww; c[t]=pp*cc; p[t]=true; } } else { t++; w[t]=ww; c[t]=cc; p[t]=0; } } for(int i=1;i<=t;i++)//枚举每一个物品 if(p[i])//若是是01 for(int j=m;j>=w[i];j--)//枚举背包容量 dp[j]=max(dp[j],dp[j-w[i]]+c[i]); //状态转移方程 else for(int j=w[i];j<=m;j++)//枚举背包容量 dp[j]=max(dp[j],dp[j-w[i]]+c[i]); //状态转移方程 printf("%d\n",dp[m]); return 0; }
混合背包,就是将多种背包混合在一块儿,先看题:混合背包
那么这种状况,咱们能够分类讨论。回望以前的01背包和彻底背包的代码,咱们会发现,只有第二重循环的顺序不一样 (废话,就是只改了哪里) 那么咱们就能够在第二重循环前判断便可。什么?你不知道哪一个是彻底背包哪一个是01背包?搞个数组标记不就好了嘛。
而后来看多重背包,那这个更好解决了!只要在输入时预处理,关键部分根本没变。
代码:
#include<bits/stdc++.h> using namespace std; int n,m,t; //n是物品数,m是背包容量 int w[6005],c[6005],p[6005]; //wi表示第i个物品的重量,ci表示价值 //pi表示状态 int dp[1005]; int main() { cin>>n>>m; for(int i=1;i<=n;i++) { int ww,cc,pp; cin>>ww>>cc>>pp; if(pp) for(int j=1;j<=pp;j++) { t++; w[t]=ww; c[t]=cc; p[t]=1; } else { t++; w[t]=ww; c[t]=cc; p[t]=0; } } for(int i=1;i<=t;i++)//枚举每一个物品 if(p[i])//若是是01 for(int j=m;j>=w[i];j--)//枚举背包容量 dp[j]=max(dp[j],dp[j-w[i]]+c[i]); //状态转移方程 else for(int j=w[i];j<=m;j++)//枚举背包容量 dp[j]=max(dp[j],dp[j-w[i]]+c[i]); //状态转移方程 cout<<dp[m]; return 0; }
持续更新中……只要这个蒟蒻学了新的背包类问题,就会更新。