• 动规复习


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

  • 相关阅读:
    ffmpeg 编译IOS静态库
    冲刺总结——第一篇
    openssl基础知识以及部分命令详解
    电子公文传输系统
    第五组课程设计—小组总结
    学习经验总结
    QtCreator:没CDB二进制档可用为二进制格式在'x86windowsmsvc2008pe32bit'"
    printf格式化输出
    DELL T110 安装windows server 2003
    visualSVN+花生壳实现外网访问局域网内SVN
  • 原文地址:https://www.cnblogs.com/very-beginning/p/13825589.html
Copyright © 2020-2023  润新知