背包问题综合

  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 }


原文地址:https://www.cnblogs.com/romaLzhih/p/11415141.html