分组背包总结

分组背包

情形1:

每组至多取一个物品。
例题:HDU1712 ACboy needs your help
状态转移方程: dp[i][j] = max( dp[i-1][j], dp[i-1][j-w[i][k]] + v[i][k]] )
方程思路: 由于是每组至多只能选一个物品可知,状态转移肯定是组间转移,而非是组内转移,考虑要到达状态dp[i][j],只能有两种情形
(1)不在第i组物品中选,继承前i-1组物品的最优解,即dp[i][j] = dp[i-1][j];
(2)在第i组物品中选一件,若选第k件即有dp[i][j] = dp[i-1][j-w[i][k]] + v[i][k]];

代码实现: 这里要考虑一个问题,如何实现每组至多只取一件物品。
 for(组:i = 1…n )
   for( 背包容量:j = m…0 )
      for(第i组中的第k个物品:k = 0… )
  由伪代码第二和第三重循环中可以看出,实现每组至多只取一件物品的做法是:先固定背包容量j,然后再遍历该组所有物品修改dp[i][j],由于每次对于第i组的所有物品修改的都是dp[i][j],若该组后面的物品是更优解,则可顶掉前面在该组中所取的物品,由此实现了每组只取一件。这种情形下的初始化问题和一般的01背包初始化并无二异。

情形2:

每组至少取一个物品。
例题:hdu3033 I love sneakers!
状态转移方程: 组内转移:dp[i][j] = max( dp[i][j], dp[i][j-w[i][k]] + v[i][k] )
        组间转移: dp[i][j] = max( dp[i-1][j], dp[i][j-w[i][k]] + v[i][k] )
方程思路: 由于每组至少选一件物品,状态转移既可以由组间转移,又可以由组内转移,即到达dp[i][j]状态既可以由 dp[i][j-w[i][k]] + v[i][k]转移而来(此时是组内转移,在第i组已经选过物品了),又可以由dp[i][j-w[i][k]] + v[i][k] 转移而来(此时是组间转移,将当下的第i组的第k件物品当作该组要选的第一件物品)。
代码实现: 这里要考虑一个问题,如何实现每组至少取一件物品。
 for(组:i = 1…n )
   for( 第i组中的第k个物品::k = 0… )
      for(背包容量:j = m…w[i][j] )
  考虑要是可以任意取,这个问题就转化成了普通了01背包问题,但是如何实现每组至少取一件呢,可以将dp[i][j](i>0)初始化为-1作为非法状态,即初始状态无法到达这一状态,将dp[0][j]初始化为0。
  若 dp[i][j-w[i][k]] = -1则说明在这之前没有到达dp[i][j-w[i][k]]状态,自然也无法从这一步进行组内转化,变成dp[i][j]状态;
  若dp[i][j-w[i][k]] = -1,则在这之前说明没有到达dp[i][j-w[i][k]] 状态,自然也无法从这一步进行组间转化,变成dp[i][j]状态。
由于除了使用数组标记是否能到达某状态以便实现每组至少选一件物品外其他和01背包的取法相同因此三重循环的顺序相当于对每组的每一件物品进行01背包。这里还需要注意的是若物品的重量可能为0,则应先进行组内转移,后进行组内转移,因为若w[i][k] = 0, v[i][k] != 0,如果先进行组间转移,即
   if(dp[i-1][j-w[i][k]] != -1)
      dp[i][j] = max( dp[i][j], dp[i-1][j-w[i][k]] + v[i][k] )
这里w[i][k] = 0, j - w[i][k] = 0,dp[i][j]经过组间转移后不为0。
再进行组内转移,即
  if(dp[i][j-w[i][k]] != -1)
    dp[i][j] = max( dp[i][j], dp[i][j-w[i][k]] + v[i][k] )
由于w[i][k] = 0, 所以经过上面的组间状态转移后dp[i][j-w[i][k]] = dp[i][j] != -1,又可以进行组内转移dp[i][j] = max( dp[i][j], dp[i][j-w[i][k]] + v[i][k] ),这样的话第i组的第k件物品选了两次,明显不合题意。

hdu3033

#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
#define mem( f, x ) memset( f, x, sizeof( f ) )
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
const int N = 105;
const int M = 1e4 + 5;
const int MAXN = 12;
int m, n, ct;
int dp[MAXN][M];

struct node{
    int w, v;
    node( ){ w = v = 0; }
    node( int ww, int vv ){ w = ww, v = vv; }
};

int main( ){
    while( scanf( "%d %d %d", &ct, &m, &n ) != EOF ){
        vector<node> p[MAXN];
        for( int i = 0; i <= m; i++ )
            dp[0][i] = 0;
        for( int i = 1; i <= n; i++ ){
            for( int j = 0; j <= m; j++ )
                dp[i][j] = -1;
        }
        
        int lb, w, v;
        for( int i = 0; i < ct; i++ ){
            scanf( "%d %d %d", &lb, &w, &v );
            p[lb].push_back( node( w, v ) );
        }
        
        for( int i = 1; i <= n; i++ ){
            int len = p[i].size( );
            for( int k = 0; k < len; k++ ){
                node tmp = p[i][k];
                for( int j = m; j >= tmp.w; j-- ){
                	//以下顺序不能变
                    if( dp[i][j-tmp.w] != -1 )//组内转移
                        dp[i][j] = max( dp[i][j], dp[i][j-tmp.w] + tmp.v );
                    if( dp[i-1][j-tmp.w] != -1 )//组间转移
                        dp[i][j] = max( dp[i][j], dp[i-1][j-tmp.w] + tmp.v );
                }
            }
        }
        
        if( dp[n][m] == -1 )
            printf( "Impossible\n" );
        else
            printf( "%d\n", dp[n][m] );
    }
    return 0;
}

另外一种写法,将不合法的状态初始化为-INF,只要中间某一步无法达到每一组至少选一件的情况则此时必有dp[i][j] < 0,这样就不必再判是否合法,直接最后判结果即可。

#include <bits/stdc++.h>
#define ll long long
#define mem( f, x ) memset( f, x, sizeof( f ) )
#define INF 0x3f3f3f3f
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
const int N = 12;
const int M = 10005;
int m, n, sn;
int dp[N][M];
struct node{
    int w, v;
    node( ){ w = v = 0; }
    node( int ww, int vv ){
        w = ww, v = vv;
    }
};

int main( ){
    while( scanf( "%d %d %d", &n, &m, &sn ) != EOF ){
        vector<node> p[N];
        int w, v, lb;
        for( int i = 0; i <= m; i++ )
            dp[0][i] = 0;
        for( int i = 1; i <= sn; i++ ){
            for( int j = 0; j <= m; j++ )
                dp[i][j] = -INF;
        }
        for( int i = 0; i < n; i++ ){
            scanf( "%d %d %d", &lb, &w, &v );
            p[lb].push_back( node( w, v ) );
        }
        for( int i = 1; i <= sn; i++ ){
            int len = p[i].size( );
            for( int k = 0; k < len; k++ ){
                node tmp = p[i][k];
                for( int j = m; j >= tmp.w; j-- ){
                    dp[i][j] = max( dp[i][j], dp[i][j-tmp.w] + tmp.v //组内 );
                    dp[i][j] = max( dp[i][j], dp[i-1][j-tmp.w] + tmp.v );//组间
                }
            }
        }
        if( dp[sn][m] < 0 )
            printf( "Impossible\n" );
        else
            printf( "%d\n", dp[sn][m] );
    }
    return 0;
}

情形3:

背包分组后可以无限制的任意选,每件只能选一次。
状态转移方程: 只需要进行组内转移:dp[i][j] = max( dp[i][j], dp[i][j-w[i][k]] + v[i][k] )

方程思路: dp[i][j]表示容量为j的背包在前i组物品中所取到的最大价值,这里的初始化区别于至少每组至少取一件的初始化问题。
  参照前面每组至少取一件的情况,由于第i组的第k件物品既可以作为本组的第一件进行组间转移,又可以作为本组的非第一件做组内转移,因此两种情况都要考虑,且当未在第i组取物品时第i组所有情况皆为非法状态,即初始化为dp[i][j = 0…m] = -1;
  每组无限制地取时,当未在第i组中取物品时,第i组仍然合法,试想若真的不在第i组中取任何一件物品,则前i组的最优解dp[i]继承前 i-1 的最优解,亦即dp[i][j = 1…m] = dp[i-1][j = 1…m],这里无限制取的情况就是01背包的情况,当遍历到第i组的第k件物品时dp[i][j]代表的就是容量为j的背包在前i-1组加上第i组的前k件物品中所取得的最优解,未变换到的背包容量继承了前i-1组的最优解dp[i-1][j],因此这里无需再次讨论dp[i-1][j-w] + v的情况。

代码实现:

//初始化
for( int j = 0; j <= m; j++ )
   dp[i][j] = dp[i-1][j];
 for( int k = 0; k < p[i].num; k++ ){
        node tmp = p[i].ve[k]; //第i组的第k件物品,体积为w,价值为v
        for( int j = m; j >= tmp.w; j-- ){
        	if( dp[i][j-tmp.w] != -1 )
            	dp[i][j] = max( dp[i][j], dp[i][j-tmp.w] + tmp.v );
        }
}

三种分组背包的综合例题: hdu3535 AreYouBusy

思路: 综合上述三种分组背包的情况分别处理即可。

#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
#define mem( f, x ) memset( f, x, sizeof( f ) )
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
const int M = 105;
const int N = 105;
const int MAXN = 32;
int m, n;
int dp[N][M];

int main( ){
    while( scanf( "%d %d", &n, &m ) != EOF ){
        for( int i = 1; i <= n; i++ ){
            int s, num;
            for( int j = 0; j <= m; j++ )
                dp[0][j] = 0;
            scanf( "%d %d", &num, &s );
            int w, v;
            if( s == 0 ){//at least
                for( int j = 0; j <= m; j++ )
                    dp[i][j] = -INF;
                for( int k = 0; k < num; k++ ){
                    scanf( "%d %d", &w, &v );
                    for( int j = m; j >= w; j-- ){
                        dp[i][j] = max( dp[i][j], dp[i][j-w] + v );
                        dp[i][j] = max( dp[i][j], dp[i-1][j-w] + v );
                    }
                }
            }
            else if( s == 1 ){ //at most
                for( int j = 0; j <= m; j++ )
                    dp[i][j] = dp[i-1][j];
                for( int k = 0; k < num; k++ ){
                    scanf( "%d %d", &w, &v );
                    for( int j = m; j >= w; j-- )
                        dp[i][j] = max( dp[i][j], dp[i-1][j-w] + v );
                }
            }
            else if( s == 2 ){//freely
                for( int j = 0; j <= m; j++ )
                    dp[i][j] = dp[i-1][j];
                for( int k = 0; k < num; k++ ){
                    scanf( "%d %d", &w, &v );
                    for( int j = m; j >= w; j-- )
                        dp[i][j] = max( dp[i][j], dp[i][j-w] + v );
                }
            }
        }
        printf( "%d\n", max( -1, dp[n][m] ) );
    }
    return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值