ZOJ Cookie Choice 多重背包 单调队列优化 分组背包 泛化物品求和

题意:一个人想买甜点。有N种不同的甜点,每种甜点有价值Ei,价格Pi。这个人对每种甜点想买Ki块。当Ki =0时,该甜点可以买无限块。

           同时,因为有些甜点的味道相同,他将有些甜点分成了G组。每组内的甜点,最多买一种。

           现在他有D元钱,想把这些钱全花光,而且最后得到的价值最大且非负,问是否有对应的方案,如果有,求出最大的价值。

思路:不同类型的背包的大拼题。

           对于不同的背包类型,我们分别处理就行了。

           因为这里的Ki,D都比较大,用二进制分解的好像就有点慢了。对于这样的最优性的多重背包,我们可以考虑用单调队列来进行优化。

           对于每一组内的东西,因为最多可以买一种,所以我们可以对每种甜点进行对应的背包,然后取最大值,得到这个组的泛化物品的价值。

           最后,就是对多件泛化物品进行合并了。对应的方程是 dp[j] = max(dp[j],dp[j-w] +v[i][k]) 0 <= w <= j

           这个时候要注意循环的顺序了。因为是用的滚动数组,我们必须要保证更新dp[j]的时候,其他值是i-1物品对应的值,否则就变成了完全背包,累加错误。

代码如下:

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>

using namespace std;

const int MAX = 1500;

int N,D,G;
int k[MAX],e[MAX],p[MAX];
int dp1[MAX],dp2[10][MAX],dp3[MAX];
int g[10][MAX],sz[10],deq[MAX][2];
bool used[MAX];

void multi_package(int * dp,int i)
{
    for(int a = 0; a < p[i]; ++a){
        int h = 0, t = 0;
        for(int j = 0; j * p[i] + a <= D; ++j){
            int val = dp[j * p[i] + a] - j * e[i];
            while(h < t && deq[h][1] <= val) t--;
            deq[t][0] = j,deq[t++][1] = val;
            dp[j * p[i] + a] = deq[h][1] + j * e[i];
            if(deq[h][0] == j - k[i]) h++;
        }
    }
}

void complete_package(int * dp, int i)
{
    for(int j = p[i]; j <= D; ++j)
        dp[j] = max(dp[j],dp[j-p[i]] + e[i]);
}

int main(void)
{
    //freopen("input.txt","r",stdin);
    while(scanf("%d%d",&N,&D) != EOF){
        memset(used,0,sizeof(used));
        memset(sz,0,sizeof(sz));
        for(int i = 0; i < N; ++i)
            scanf("%d%d%d",&k[i],&e[i],&p[i]);
        scanf("%d",&G);
        for(int i = 0 ; i < G; ++i){
            int d; char ch;
            while(scanf("%d%c",&d,&ch)){
                g[i][sz[i]++] = --d;
                used[d] = true;
                if(ch == '\n')
                    break;
            }
        }
        memset(dp1,0xcf,sizeof(dp1));
        dp1[0] = 0;
        for(int i = 0; i < N; ++i){
            if(used[i]) continue;
            if(k[i])
                multi_package(dp1,i);
            else
                complete_package(dp1,i);
        }
        memset(dp2,0xcf,sizeof(dp2));
        for(int id = 0; id < G; ++id){
            for(int i = 0; i < sz[id]; ++i){
                memset(dp3,0xcf,sizeof(dp3));
                dp3[0] = 0;
                if(k[g[id][i]])
                    multi_package(dp3,g[id][i]);
                else
                    complete_package(dp3,g[id][i]);
                for(int i = 0; i <= D; ++i)
                    dp2[id][i] = max(dp2[id][i],dp3[i]);
            }
        }
        for(int id = 0; id < G; ++id)
            for(int j = D; j >= 0; --j)
                for(int w = 0; w <= j; ++w)
                    dp1[j] = max(dp1[j],dp1[j-w] + dp2[id][w]);
        if(dp1[D] < 0)
            puts("i'm sorry...");
        else
            printf("%d\n",dp1[D]);
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值