背包
01 背包
\(n\) 件物品,每件物品有权值和重量,给出背包体积 \(V\),从这些物品中挑选若干件(只能选一次)放入背包,使得背包内物品的总重量不超过 \(V\),问能可以得到的最大权值。
设 \(dp[i][j]\) 选取前 \(i\) 件物品重量为 \(j\) 能取得的最大的权值,可以得到转移方程 \(dp[i][j]=max(dp[i-1][j-w[i]]+val[i],dp[i-1][j]);\)
因为 i 只与 i-1 有关所以考虑滚动数组:
交替滚动
用 now 表示现在的数组,用 old 表示上一个数组,交替使用。
#include<bits/stdc++.h>
using namespace std;
const int N=10010;
int t,m;
int w[202],val[202];
int dp[3][10010];
int main(){
ios::sync_with_stdio(false);
cin>>t>>m;
for(int i=1;i<=m;i++){
cin>>w[i]>>val[i];
}
int now=0,old=1;
for(int i=1;i<=m;i++){
swap(old,now);
for(int j=0;j<=t;j++){
if(j>=w[i]){
dp[now][j]=max(dp[old][j],dp[old][j-w[i]]+val[i]);
}
else{
dp[now][j]=dp[old][j];
}
}
}
cout<<dp[now][t];
return 0;
}
自我滚动
因为体积只会向小减少,所以从后向前遍历容量以确保每个数物品只选一次,上一次的数组储存的数(i-1) 结果全部就在这个数组里,这样空间压缩到一维。
#include<bits/stdc++.h>
using namespace std;
const int N=10010;
int t,m;
int w[202],val[202];
int dp[10010];
int main(){
ios::sync_with_stdio(false);
cin>>t>>m;
for(int i=1;i<=m;i++){
cin>>w[i]>>val[i];
}
for(int i=1;i<=m;i++){
for(int j=t;j>=w[i];j--){
dp[j]=max(dp[j],dp[j-w[i]]+val[i]);
}
}
cout<<dp[t];
return 0;
}
完全背包
\(n\) 件物品,每件物品有权值和重量,给出背包体积 \(V\),从这些物品中挑选若干件(可以多次同选一个)放入背包,使得背包内物品的总重量不超过 \(V\),问能可以得到的最大权值。
我们对 01 背包的代码进行更改为从小到大枚举容量,因为在计算某个容量时,可以确保之前计算的状态已经包含了该物品之前选择的次数,这样就能够正确地累积多个相同物品的价值。
#include<bits/stdc++.h>
using namespace std;
const int N=10010;
int t,m;
int w[202],val[202];
int dp[10010];
int main(){
ios::sync_with_stdio(false);
cin>>t>>m;
for(int i=1;i<=m;i++){
cin>>w[i]>>val[i];
}
for(int i=1;i<=m;i++){
for(int j=w[i];j<=t;j++){
dp[j]=max(dp[j],dp[j-w[i]]+val[i]);
}
}
cout<<dp[t];
return 0;
}
多重背包
\(n\) 件物品,每件物品有权值和重量,给出背包体积 \(V\),从这些物品中挑选若干件(给出共有几个)放入背包,使得背包内物品的总重量不超过 \(V\),问能可以得到的最大权值。
考虑将每个物品进行二进制拆分,拆完后就转化为 01 背包问题。(因为是二进制,所以可以保证可以取到任意组合)
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int t,m;
int w[N],val[N];
long long dp[N];
int main(){
ios::sync_with_stdio(false);
cin>>m>>t;
int a,b,c;
int cnt=0;
for(int i=1;i<=m;i++){
cin>>a>>b>>c;
for(int j=1;j<=c;j<<=1){//二进制
w[++cnt]=j*a;//第cnt个背包的重量
val[cnt]=j*b;//计算权值
c-=j;//计算剩余
}
if(c){//剩余单独储存
w[++cnt]=c*a;
val[cnt]=c*b;
}
}
for(int i=1;i<=cnt;++i){//01背包选择
for(int j=t;j>=w[i];--j){
dp[j]=max(dp[j],dp[j-w[i]]+val[i]);
}
}
cout<<dp[t];
return 0;
}
分组背包
01背包规则,他的物品大致可分为 \(k\) 组,每组中的物品相互冲突,现在,问最大的价值是多少。
只是加入了组的限制,只需按组储存,然后 01 背包。
#include<bits/stdc++.h>
using namespace std;
int n,m;
int w[10001];
int val[10001];
int b[100001];
int g[205][205];
int dp[100001];
int main(){
ios::sync_with_stdio(false);
cin>>m>>n;
int t=0;
for(int i=1;i<=n;i++){
int x;//组
cin>>w[i]>>val[i]>>x;
t=max(t,x);//最大组
b[x]++;//小组物品增加
g[x][b[x]]=i;//第 x 组第 b[x] 个物品的编号
}
for(int i=1;i<=t;i++){//枚举组数
for(int j=m;j>=0;j--){//枚举容量
for(int k=1;k<=b[i];k++){//枚举第i组的物品
if(j>=w[g[i][k]]){
dp[j]=max(dp[j],dp[j-w[g[i][k]]]+val[g[i][k]]);
}
}
}
}
cout<<dp[m];
return 0;
}
混合背包
背包中的物品有的可以选一次(0/1 背包),有的可以选多次(完全背包),有的数量有限(多重背包)。
无限个并不是真的无限,只需转化为最大容积除物品中重量的个数就行,然后问题就转化为多重背包了。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int t,m;
int w[N],val[N];
long long dp[N];
int main(){
ios::sync_with_stdio(false);
cin>>t>>m;
int a,b,c;
int cnt=0;
for(int i=1;i<=m;i++){
cin>>a>>b>>c;//重,价,数
if(c==0){//无限个(最大容积除重量个)
c=t/a;
}
for(int j=1;j<=c;j<<=1){//剩下多重背包
w[++cnt]=j*a;
val[cnt]=j*b;
c-=j;
}
if(c){
w[++cnt]=c*a;
val[cnt]=c*b;
}
}
for(int i=1;i<=cnt;++i){
for(int j=t;j>=w[i];--j){//01背包
dp[j]=max(dp[j],dp[j-w[i]]+val[i]);
}
}
cout<<dp[t];
return 0;
}