poj 1014 Dividing(组合数学方法优化/多重背包问题+二进制优化)

poj 1014 Dividing(组合数学方法优化/多重背包问题+二进制优化)
总时间限制: 1000ms 内存限制: 65536kB

描述
Marsha and Bill own a collection of marbles. They want to split the collection among themselves so that both receive an equal share of the marbles. This would be easy if all the marbles had the same value, because then they could just split the collection in half. But unfortunately, some of the marbles are larger, or more beautiful than others. So, Marsha and Bill start by assigning a value, a natural number between one and six, to each marble. Now they want to divide the marbles so that each of them gets the same total value. Unfortunately, they realize that it might be impossible to divide the marbles in this way (even if the total value of all marbles is even). For example, if there are one marble of value 1, one of value 3 and two of value 4, then they cannot be split into sets of equal value. So, they ask you to write a program that checks whether there is a fair partition of the marbles.

输入
Each line in the input file describes one collection of marbles to be divided. The lines contain six non-negative integers n1 , … , n6 , where ni is the number of marbles of value i. So, the example from above would be described by the input-line “1 0 1 2 0 0”. The maximum total number of marbles will be 20000.
The last line of the input file will be “0 0 0 0 0 0”; do not process this line.

输出
For each collection, output “Collection #k:”, where k is the number of the test case, and then either “Can be divided.” or “Can’t be divided.”.
Output a blank line after each test case.

样例输入
1 0 1 2 0 0
1 0 0 0 1 1
0 0 0 0 0 0

样例输出
Collection #1:
Can’t be divided.

Collection #2:
Can be divided.

来源
Mid-Central European Regional Contest 1999

好题,题意非常明晰。

乍一看可以直接dp,把本题当作多重背包看待,我们容易知道多重背包可以转化为一重背包。这就是version 1没有加组合数学优化的代码,可以看到这种朴素的思想,结果毫无悬念的TLE了。
这是因为数据一大物品个数就很多了。时间复杂度为O(n^2),物品总个数为n,则共n次,每一次更新每一个价值,价值量与n差常数。n事实上会达到2*10^4而价值总量有6*10^4,所以每组数据总共有10^9的计算量,而且有一个常数,多组数据,理论上一定TLE。

那么优化是必须的,至于如何优化有三种思想:
一.搜索与贪心结合,先放大的再放小的,很遗憾,这个算法正确性不能保证。网上流行的dfs有一个问题,它本质是贪心没有回溯过程(加上回溯了肯定TLE),下面数据先放大再放小贪心是过不了的 0 0 3 0 0 3 1,之所以能过是因为数据太弱了。我仔细研读网上的dfs代码后放弃了这个优化思路。
二.这是一个组合数学方法优化,来自于程序设计实习教学ppt上,证明了(或者观察出了)一个定理:如果可以均分,那么每种珠子数目mod 12也可以均分(大一点是24,36也没关系)。证明较难,见教学ppt。而且我对做题能想到这个几乎不抱希望…如果一定要说学习到什么的话,只能说以后一旦认定数学题要多多大胆猜想一下这种结论是否存在。利用这种优化就得到version 1。
三.在多重背包dp上做技巧,注意到原来把一个k重背包化为k个一种背包,如果用二进制优化,只需要化为logk数量级的一重背包,具体方式是背包权值乘以1,2,4,8,…,2^t,k+1-2^(t+1),2^t< k<=2^(t+1)。容易证明也容易实现。利用这种优化就得到version 2。

下面有一篇文章讲背包问题非常全面,也很简洁,三就是看了这篇文章才理解,太妙了。
http://blog.csdn.net/lyhvoyage/article/details/8545852

version 1

#include<stdio.h>
#include<memory.h>

int n[7],v,cases=1,max;
bool f[60010];

int main()
{
    while (scanf("%d%d%d%d%d%d",n+1,n+2,n+3,n+4,n+5,n+6))
    {
        v=0;
        max=0;
        //组合数学优化 
        for (int i=1;i<=6;i++)
            n[i]%=12;
        //组合数学优化 
        for (int i=1;i<=6;i++)
            v+=i*n[i];
        if (v==0)
            return 0;
        if (cases!=1)
            printf("\n");
        if (v&1)
        {
            printf("Collection #%d:\nCan't be divided.\n",cases++);
            continue;
        }
        memset(f,0,sizeof(f));
        f[0]=true;
        for (int i=1;i<=6;i++)
            for (int t=1;t<=n[i];t++)
                for (int j=max;j>=0;j--)
                {
                    f[j+i]=f[j+i]||f[j];
                    max+=i;
                    if (max>v/2)
                        max=v/2; 
                }
        if (f[v/2])
            printf("Collection #%d:\nCan be divided.\n",cases++);
        else
            printf("Collection #%d:\nCan't be divided.\n",cases++);
    }
} 

version 2

#include<stdio.h>
#include<memory.h>

int n[7],sum_v,cases=1,max,v[100],m,power,sum;
bool f[60010];

int main()
{
    while (scanf("%d%d%d%d%d%d",n+1,n+2,n+3,n+4,n+5,n+6))
    {
        sum_v=0;
        max=0;
        for (int i=1;i<=6;i++)
            sum_v+=i*n[i];
        if (sum_v==0)
            return 0;
        if (cases!=1)
            printf("\n");
        if (sum_v&1)
        {
            printf("Collection #%d:\nCan't be divided.\n",cases++);
            continue;
        }
        m=0;
        for (int i=1;i<=6;i++)
            if (n[i]) 
            {
                power=1;
                sum=0;
                while (n[i]>sum+power)
                {
                    m++;
                    v[m]=power*i;
                    sum+=power;
                    power<<=1;
                }
                m++;
                v[m]=i*(n[i]-sum);
            }
        /*
        for (int i=1;i<=m;i++)
            printf("%d  ",v[i]);
        */
        memset(f,0,sizeof(f));
        f[0]=true;
        for (int j=1;j<=m;j++)
            for (int t=sum_v/2-v[j];t>=0;t--)
                if (f[t])
                    f[t+v[j]]=true;
        if (f[sum_v/2])
            printf("Collection #%d:\nCan be divided.\n",cases++);
        else
            printf("Collection #%d:\nCan't be divided.\n",cases++);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值