[HDU 1059] Dividing (多重背包+二进制优化)

链接

HDU 1059


题意

给出6种硬币,价值分别为[1, 6],每种硬币有xi个,问是否能将这些硬币分为两组,每组硬币的价值为总价值的一半。


题解

是一种简化版的多重背包问题。

先考虑这样一个问题,给出n个物品,每个物品占用空间为vi,问是否能选择若干物品能填满容量为V的背包。
这是一个01背包问题,但是没价值,只有容量,怎么办?
考虑将问题“复杂化”,每个物品的价值为1,问装满容量为V的背包需要最多多少件物品,无法装满输出-1。
貌似会做了,01背包的裸题,最开始将除容量d[0]外的非法状态设为-INF,进行转移,如果有解,那么d[V]最终一定是非负的。这里注意要“复杂化”成求“最多”物品,因为非法状态是-INF。

回到原来的问题,现在是n种物品,每种mi个,怎么办?
可以将mi个物品看开,这样就变成了01背包问题,但是代价比较大。
如果能将这mi个物品分成若干组(不能太多),并且这些分组的组合能构造出[1, mi]任意个物品那就好了。
的确有这种分法,将mi分为2^0, 2^1, …, 2^(k-1), mi - 2^k + 1即可。
k是保证最后一项>=0的最大值,可以证明这些数可以构造出[1, mi]中任意一个,代码也很好写,将一个值拆成了logm个,效率很高了。


代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long lint;
const lint oo = 1e16;
int a[10], kase = 0;
int W[200], V[200], cnt;
lint dp[130000];
int main()
{
    while(cin >> a[1])
    {
        int sum = a[1];
        for(int i = 2; i <= 6; i++)
        {
            scanf("%d", &a[i]);
            sum += i * a[i];
        }
        if(0 == sum) break;
        if(sum % 2) { printf("Collection #%d:\nCan't be divided.\n\n", ++kase); continue; }
        cnt = 0;
        for(int i = 1, t, num; i <= 6; i++)
        {
            num = a[i], t = 1;
            while(num >= t)
            {
                W[cnt] = t;
                V[cnt++] = i * t;
                num -= t;
                t <<= 1;
            }
            if(num > 0) { W[cnt] = num; V[cnt++] = i * num; }
        }
        for(int i = 1; i <= sum / 2; i++)
            dp[i] = -oo;
        dp[0] = 0;
        for(int i = 0; i < cnt; i++)
        {
            for(int j = sum / 2; j >= 0; j--)
            if(j >= V[i]) dp[j] = max(dp[j], dp[j - V[i]] + W[i]);
        }
        printf("Collection #%d:\n", ++kase);
        if(dp[sum / 2] < 0)
            printf("Can't be divided.\n\n");
        else
            printf("Can be divided.\n\n");
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值