背包dp
01背包
问题:有(n)个物品,(m)的容量,每个物品只能选一次,且已知每个物品的重量(w_i),价值(v_i),求所取物品的最大价值。
定义状态:设(f_{i,j})为在前(i)个物品里选,容量为(j)的被背包所能达到的最大总价值
状态转移:(f_{i,j}=max(f_{i-1,j},f_{i-1,j-w_i}+v_i))
( ext{表示不选当前物品和选择当前物品的两种情况做一个比较})
优化:由于对(f_i)有影响的只有(d_{i-1}),可以去掉第一维,直接用(f_i)来表示处理到当前物品是背包容量为(i)的最大价值
状态转移:(f_j=max(f_j,f_{j-w_i}+v_i))
代码模板:
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
const int maxn=1e5+10;
int n,m;
int w[maxn],v[maxn],dp[maxn];
int main(){
scanf ("%d%d",&n,&m);
for (int i = 1;i <= n;i++) scanf ("%d%d",&w[i],&v[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]]+v[i]);
printf("%d
",dp[m]);
}
完全背包
问题:与01背包不同,每个物品可以选择无数次,求选择物品的最大价值
定义状态:(f_{i,j})表示在前(i)个物品选,容量为(j)的背包可以达到的最大价值
状态转移:(f_{i,j}=max_{k=0}^{+infty}(f_{i-1,j-k imes w_i}+v_i imes k))
( ext{其实只是在最外层加了一个数量的限制})
优化后的状态转移:(f_{i,j}=max(f_{i-1,j},f_{i,j-w_i}+v_i))
代码模板:
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
const int maxn=1e5+10;
int n,m;
int w[maxn],v[maxn],dp[maxn];
int main(){
scanf ("%d%d",&n,&m);
for (int i = 1;i <= n;i++) scanf ("%d%d",&w[i],&v[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]]+v[i]);
printf("%d
",dp[m]);
}
多重背包
问题:多重背包也会01背包的一个变式,与01背包的区别在于规定每种物品最多有(k_i)个
状态转移:(f_{i,j}=max_{k=0}^{k_i}(f_{i-1,j-k imes w_i}+v_i imes k))
二进制优化:在朴素做法中,因为同一个组中物品是相同的,所以考虑,同时选择(A_{i,2},A_{i,2})和(A_{i,3},A_{i,4})是一样的,这样我们就做了大量重复的工作,那么优化拆分方式就成为了解决问题的突破口,因为每个十进制数都可以转化成二进制,所以每个数都可以转化成(2^1+2^2+2^3+……+2^n)的形式,任意(1~k)内的数字都可以被表示出来,这样(k)个物品里,选择不同数量物品的情况就都可以被表示出来
代码模板:
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
const int maxn=1e5+10;
int n,m;
int w[maxn],v[maxn],dp[maxn];
int main(){
scanf ("%d%d",&n,&m);
int a,b,c,tot=1;
for (int i = 1;i <= n;i++){
scanf ("%d%d%d",&a,&b,&c);
for (int j = 1;j <= c;j<<=1){
v[++tot]=j*a,w[tot]=j*b;
c-=j;
}
if (c) v[++tot]=a*c,w[tot]=b*c;
}
for (int i = 1;i <= tot;i++)
for (int j = m;j >= w[i];j--)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
printf("%d
",dp[m]);
}
二维费用背包
问题:与01背包类似,但是不同的地方在于选择一个物品会消耗两种代价
思路:方程基本不变,只需要再开一维数组,同时转移两个代价就好了
代码模板:
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
const int maxn=1e3+10;
int n,m,t;
int dp[maxn][maxn],w[maxn],tim[maxn];
int main(){
scanf ("%d%d%d",&n,&m,&t);
for (int i = 1;i <= n;i++) scanf ("%d%d",&w[i],&tim[i]);
for (int i = 1;i <= n;i++)
for (int j = m;j >= w[i];j--)
for (int k = t;k >= tim[i];k--)
dp[j][k]=max(dp[j-w[i]][k-tim[i]]+1,dp[j][k]);
printf("%d
",dp[m][t]);
return 0;
}
分组背包
问题:所谓分组背包就是将物品分组,每组的物品相互冲突,最多只能选一个物品放进去
思路:相当于在所有物品中选择一件,变成了从当前组中选择一件,于是对每一组进行一次01背包就可以了
定义状态:(num_{k,i})表示第(k)组的第(i)件物品的编号是多少,再用(sum_k)表示第(k)组物品有多少个
代码模板:
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
const int maxn=2000;
int n,m;
int a,b,c,k;
int dp[maxn],w[maxn],val[maxn];
int num[maxn][maxn],sum[maxn];
int main(){
scanf("%d%d",&m,&n);
for (int i = 1;i <= n;i++){
scanf ("%d%d%d",&w[i],&val[i],&c);
sum[c]++; num[c][sum[c]]=i;
k=max(k,c)
}
for (int l = 1;l <= k;l++){
for (int i = m;i >= 1;i--){
for (int j = 1;j <= sum[l];j++){
if (i>=w[num[l][j]]) dp[i]=max(dp[i],dp[i-w[num[l][j]]]+val[num[l][j]]);
}
}
}
printf("%d
",dp[m]);
return 0;
}
背包变种
输出方案
思路:输出方案其实就是记录背包是从哪个状态转移过来的,我们可以用(g_{i,v})