动规复习

背包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})

原文地址:https://www.cnblogs.com/very-beginning/p/13825589.html