• 背包问题综合


    1. 01背包
      问题:若干个物体,每个价值为c[i]重量为w[i],数量均为1,背包最大容量为W,问怎样取物体才能最大化收益?
      解法:dp[i][j]以j为容量为放入前i个物品(按i从小到大的顺序)的最大价值。有递推关系式:dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+c[i])。这里的第一维度可以省略。
      注意这里的内存重量循环j为W到0,递减关系(保证每个物体取一遍)(后面的不会修改前面的)
      当背包空间j少于当前物体的重量时不选该物体这一步可以省略,对后续没有影响。
      最终写法为:
      1  for(int i=1; i<=m; i++) //物品 
      2         for(int j=W; j>=0; j--) //容量 
      3         {
      4             if(j >= w[i])
      5                 dp[i][j] = max(dp[i-1][j-w[i]]+val[i], dp[i-1][j]);
      6             else      //只是为了好理解
      7                 dp[i][j] = dp[i-1][j];           
      8         }

       空间优化:

      1 for(i=1;i<=m;i++){  //尝试放置每一个物品
      2   for(j=t;j>=w[i];j--){//倒叙是为了保证每个物品都使用一次
      3       f[j]=max(f[j-w[i]]+v[i],f[j]);
      4 //在放入第i个物品前后,检验不同j承重量背包的总价值,如果放入第i个物品后比放入前的价值提高了,则修改j承重量背包的价值,否则不变
      5   }
      6 }
    2. 完全背包
      问题:若干个物体,每个价值为c[i]重量为w[i],数量均为无限,背包最大容量为W,问怎样取物体才能最大化收益?
      解法:dp[i][j]以j为容量为放入前i个物品(按i从小到大的顺序)的最大价值。有递推关系式:dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+c[i])。这里的第一维度可以省略。
      注意这里的内存重量循环j为w[i]到W,递增关系(保证每个物体取一遍)(后面的不会修改前面的)
      最终写法为:
      1  for(int i=1; i<=n; i++)
      2         for(int j=w[i]; j<=W; j++)//注意此处,与0-1背包不同,这里为顺序,0-1背包为逆序
      3             f[j]=max(f[j],f[j-w[i]]+c[i]);


      例题:POJ2229
      题意:
      找一些2^x(0<=x),使它们的和为N。比如,N=7: 
      1) 1+1+1+1+1+1+1 
      2) 1+1+1+1+1+2 
      3) 1+1+1+2+2 
      4) 1+1+1+4 
      5) 1+2+2+2 
      6) 1+2+4 
      (1 <= N <= 1,000,000). 
      解法:直接完全背包即可,注意关系式更新的时候为“拿”的情况加上“不拿”的情况;注意初始化dp[0]=1

       1 int N;
       2 int num[25];
       3 int dp[1000000 + 5];
       4 int main() {
       5     // freopen("input.txt", "r", stdin);
       6     scanf("%d", &N);
       7     num[1] = 1;
       8     dp[0] = 1;
       9     for (int i = 2; i <= 25; i++) num[i] = num[i - 1] * 2;
      10     for (int i = 1; i <= 21; i++) {
      11         for (int j = num[i]; j <= N; j++) {
      12             dp[j] = (dp[j] + dp[j - num[i]]) % MOD;
      13         }
      14     }
      15     printf("%d
      ", dp[N]);
      16     return 0;
      17 }
    3. 多重背包
      多重背包里面每个物品的数量是有限的,即k个,因此基本的求解思路是将其转换为01背包再求解;或者枚举k的个数。
      v[i],w[i],与c[i],分别表示这种物品的体积、价值和件数。
      1     for (int i = 1; i <= n; i++)
      2         for (int j = m; j >= c[i]; j--)
      3             for (int k = 0; k <= num[i]; k++)
      4                 if (j - c[i] * k >= 0)
      5                     f[j] = max(f[j], f[j - c[i] * k] + v[i] * k);

      当然直接这样做一定会超时,其复杂度为$O(Vsum_{}^{}n[i])$。

      优化方法第一种是改变分割方法(二进制),即:将第 i 种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为 $1 , 2 , 4 ,..., 2^{k-1}, n[i]-2^{k}+1$,且k 是满足 n[i] - 2^{k}+1>0 的最大整数。例如,如果 n[i] 为13,就将这种物品分成系数分别为 1 , 2 , 4 , 6 的四件物品。 
      分成的这几件物品的系数和为 n[i],表明不可能取多于 n[i] 件的第 i 种物品。另外这种方法也能保证对于 0 .. n[i] 间的每一个整数,均可以用若干个系数的和表示,这个证明可以分 0.. 2^{k}-1 和 2^{k}...n[i] 两段来分别讨论得出。
      这样就将第i种物品分成了 O(logn[i]) 种物品,将原问题转化为了复杂度为 O(V*∑log n[i]) 的01背包问题。

       1     for (int i = 1; i <= n; i++) {
       2         int x = read(), y = read(), z = read();
       3         int base = 1;
       4         while (z >= base) {
       5             c[++tot] = x * base;
       6             v[tot] = y * base;
       7             z -= base;
       8             base <<= 1;
       9         }
      10         if (z > 0) {
      11             c[++tot] = z * x;
      12             v[tot] = z * y;
      13         }
      14     }
      15     for (int i = 1; i <= tot; i++)
      16         for (int j = m; j >= c[i]; j--) 
      17         f[j] = max(f[j], f[j - c[i]] + v[i]);

      优化方法第二种是采用优先队列的方法,推导方法及其神奇,看不懂了,挂个链接
      复杂度为 O(nV)

       1 for(int i=1;i<=n;i++)//枚举物品种类 
       2 {
       3     cin>>c[i]>>w[i]>>num[i];//c,w,num分别对应 体积,价值,个数 
       4     if(V/c[i] <num[i]) num[i]=V/c[i];//求lim
       5     for(int mo=0;mo<c[i];mo++)//枚举余数 
       6     {
       7         head=tail=0;//队列初始化 
       8         for(int k=0;k<=(V-mo)/c[i];k++) 
       9         {
      10             int x=k;
      11             int y=f[k*c[i]+mo]-k*w[i];
      12             while(head<tail && que[head].pos<k-num)head++;//限制长度
      13             while(head<tail && que[tail-1].value<=y)tail--;
      14             que[tail].value=y,que[tail].pos=x;
      15             tail++;
      16             f[k*c[i]+mo]=que[head].value+k*w[i];
      17             //加上k*w[i]的原因:
      18             //我们的单调队列维护的是前i-1种的状态最大值.
      19             //因此这里加上k*w[i].
      20         }
      21     }
      22 }


      例题:

      POJ1742
      题意:有 n 种面额的硬币,面额个数分别为v[i]、num[i],求最多能搭配出几种不超过 m 的金额?
      解法:没有时间、空间优化都会炸。所以采用多重背包加上规则定义求解,定义如下:

      ①首先来看看朴素的方法:

      bool dp[i][j] := 用前 i 种硬币能否凑成j

      递推关系式:

      dp[i][j] = (存在 k 使得 dp[i – 1][j – k * v[i]] 为真,0 <= k <= C_i 下标合法)

      然后三重循环 i j k 递推

      这样的话复杂度为O(m*ΣC_i),肯定过不去。。

      ②然后我们想到将问题转化为01背包,并利用二进制(具体可以看背包九讲)来优化

      复杂度为O(m*ΣlogC_i),仍然TLE。。

      ③我们优化dp的状态:

      状态:dp[i][j] : = 用前 i 种硬币凑成 j 时第 i 种硬币最多能剩余多少个( - 1表示配不出来)

      转移:

      ①若 $dp[i-1][j]>=0$,即前 $i-1$ 种可以配成 $j$,所以根本用不到第$i$种,所以剩余$nun[i]$种  $dp[i][j]=nun[i]$

      ②若$j<a[i] || dp[i][j-a[i]]<=0$,由于$dp[i-1][j]<0$,所以要想配成$j$起码得要有第$i$种,若$j<a[i]$则第$i$种用不到,所以前$i$种仍配不到$j$,若$dp[i][j-a[i]]<=0$,则说明配成$j-a[i]$时第$i$种已经无剩余或者甚至无法配成$j-a[i]$,更别说配成$j$了,        $dp[i][j]=-1$

      ③其他情况,由于$a[i]$还有剩,所以$dp[i][j]$相当于在$dp[i][j-a[i]]$的基础上多使用了一个$a[i]$,此时  $ dp[i][j]=dp[i][j-a[i]]-1$

       1 int v[106], num[106];
       2 int dp[MAXN];
       3 int n, m;
       4 int main() {
       5     // freopen("input.txt", "r", stdin);
       6     while (scanf("%d%d", &n, &m) && n && m) {
       7         CLR(v), CLR(num);
       8         SET(dp);
       9         REP(i, 1, n) v[i] = READ();
      10         REP(i, 1, n) num[i] = READ();
      11         dp[0] = 0;
      12         int ans = 0;
      13         REP(i, 1, n) REP(j, 0, m) {
      14             if (dp[j] >= 0)
      15                 dp[j] = num[i];
      16             else if (j < v[i] || dp[j - v[i]] < 0)
      17                 dp[j] = -1;
      18             else
      19                 dp[j] = dp[j - v[i]] - 1;
      20             if (i == n && j > 0) ans += dp[j] >= 0;
      21         }
      22         int a = 1;
      23         cout << ans << endl;
      24     }
      25 
      26     return 0;
      27 }


  • 相关阅读:
    How to become a hacker
    逻辑地址、线性地址、物理地址和虚拟地址
    java configuration
    Java 理论与实践: 变还是不变?
    Teach Yourself Programming in Ten Years
    border padding margin , the difference among them
    hashCode方法,equals方法,HashSet,HasMap之间的关系
    工程名 显示红色叹号
    记The_C_Programming_Language的错误
    VIM简单介绍学习1
  • 原文地址:https://www.cnblogs.com/romaLzhih/p/11415141.html
Copyright © 2020-2023  润新知