poj 2754/1014 多重背包之二进制优化

2754题意:给定M(1<=M<=200)以及长度为M的四个数组,分别记为Pairs、Multi、Low、Up,你需要构造一个长度为M的数组Table(其中Low[i]<=Table[i]<=Up[i]),满足∑Multi[i]*Table[i] = 0,且使得∑Pairs[i]*Table[i]尽量大。输入满足至少有一个解。

思路:如果直接上dp,那么复杂度最大可达M*极差*50。其中M是序列长度,50是因为每个数最多有50个取值的可能,极差是∑Multi[i]*Table[i]能达到的最大值和最小值之间的差值。无法承受。下面考虑将其转化为多重背包问题。

将下界单独拿出来作为一部分进行计算,因此[Low[i],Up[i]]就转化为[0, U[i]-L[i]]的一个多重背包。M[i]和P[i]均进行单独的下界计算。计算出 T = L[1]*M[1]+L[2]*M[2].... 之后, 就是一个关于容量T刚好放满的多重背包。

多重背包有一个二进制优化,参见(http://www.cnblogs.com/favourmeng/archive/2012/09/07/2675580.html)。

#include <cstdio>
#include <algorithm>
#include <cstdlib>
#define N 205
#define ORI 50000
#define INF 0x7fffffff
using namespace std;
int dp[200005];
int n,m;
int up[N],low[N],p[N],w[N];
int main(){
    while(scanf("%d",&n) != EOF){
        m = 0;
        int presum = 0;
        for(int i = 0;i<n;i++){
            scanf("%d %d %d %d",&p[i],&w[i],&low[i],&up[i]);
            m += w[i]*low[i];
            presum += p[i]*low[i];
            up[i] -= low[i];
        }
        m = -m;
        for(int i = 1;i<=m;i++)
            dp[i] = -INF;
        dp[0] = 0;
        for(int i = 0;i<n;i++){
            int num = 1;
            while(up[i] > num){
                for(int j = m;j>=num*w[i];j--)
                    dp[j] = max(dp[j], dp[j-num*w[i]]+num*p[i]);
                up[i] -= num;
                num <<= 1;
            }
            for(int j = m;j>=up[i]*w[i];j--)
                dp[j] = max(dp[j], dp[j-w[i]*up[i]]+p[i]*up[i]);
        }
        printf("%d\n",dp[m]+presum);
        
    }
    return 0;
}

1014题意:有六种石头,分别价值1~6,现在给出价值分别为1~6的石头的数量(石头总数不超过20000)。问能否把这些石头分成两类,使得每类的价值总和相等。

思路:首先求价值总和,如果是奇数必然不能。否则就变成了一个多重背包问题,看看能否凑成总和的一半,用二进制优化。

#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <vector>
#include <queue>
#include <algorithm>
#include <cmath>
#include <map>
#include <set>
#include <iostream>
#define N 120005
#define INF 0x3fffffff
int s[7],c = 1;
bool dp[N>>1];
using namespace std;
int main(){
    while(1){
        int m = 0;
        for(int i = 1;i<=6;i++){
            scanf("%d",&s[i]);
            m += i*s[i];
        }
        if(m == 0)
            break;
        if(m & 1)
            printf("Collection #%d:\nCan't be divided.\n\n",c++);
        else{
            memset(dp,false,sizeof(dp));
            dp[0] = true;
            m >>= 1;
            for(int i = 1;i<=6;i++){
                if(!s[i])
                    continue;
                int num = 1;
                while(s[i] > num){
                    for(int j = m;j-num*i>=0;--j)
                        dp[j] |= dp[j - num*i];
                    s[i] -= num;
                    num <<= 1;
                }
                for(int j = m; j - s[i]*i>=0; --j)
                    dp[j] |= dp[j - s[i]*i];
            }
            if(dp[m])
                printf("Collection #%d:\nCan be divided.\n\n",c++);
            else
                printf("Collection #%d:\nCan't be divided.\n\n",c++);
        }
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值