poj 1014 Dividing

Dividing
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 46529 Accepted: 11617

Description

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.

Input

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.

Output

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.

Sample Input

1 0 1 2 0 0 
1 0 0 0 1 1 
0 0 0 0 0 0 

Sample Output

Collection #1:
Can't be divided.

Collection #2:
Can be divided.

Source


直接用dp做会超时,两种TLE的代码:
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
int dp[200000];
int v[200000],w[7];
int main()
{
    int cases=0;
    while(scanf("%d%d%d%d%d%d",&w[1],&w[2],&w[3],&w[4],&w[5],&w[6]),
          w[1]||w[2]||w[3]||w[4]||w[5]||w[6])
    {
        int t=0,sum=0;
        printf("Collection #%d:\n",++cases);
        for(int i=1;i<=6;i++)
        {
            sum+=i*w[i];
            for(int j=w[i];j>0;j--)
            {
               v[++t]=i;
            }
        }
        if(sum%2==1)
        printf("Can be divided.\n\n");
        for(int i=1;i<=t;i++)
        for(int j=sum/2;j>=v[i];j--)
        dp[j]=max(dp[j],dp[j-v[i]]+v[i]);
        if(dp[sum/2]==sum/2)
        printf("Can be divided.\n\n");
        else
        printf("Can't be divided.\n\n");
    }
    return 0;
}

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
int dp[2001000];
int v[7];
int w[7]={0,1,2,3,4,5,6};
int main()
{
    int t=0;
    while(1)
    {
        int sum=0;
        for(int i=1;i<=6;i++)
        {
            scanf("%d",&v[i]);
            sum+=v[i]*w[i];
        }
        if(sum==0)
        break;
        memset(dp,0,sizeof(dp));
        printf("Collection #:%d\n",++t);
        if(sum%2==1)
        {
            printf("Can't be divided.\n\n");
            continue;
        }
        for(int i=1;i<=6;i++)
        for(int j=1;j<=v[i];j++)
        for(int k=sum/2;k>=w[i];k--)
        dp[k]=max(dp[k],dp[k-w[i]]+w[i]);
        if(dp[sum/2]==sum/2)
        printf("Can be divided.\n\n");
        else
        printf("Can't be divided.\n\n");
    }
    return 0;
}

发现还是不行,所以用多重背包里的二进制拆分,直接套模板:

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
int dp[20001*6];
int c[7],sum;
void zero_one(int weight,int value)
{
    for(int i=sum;i>=weight;i--)
    dp[i]=max(dp[i],dp[i-weight]+value);
}
int main()
{
    int count=0,k;
    while(scanf("%d%d%d%d%d%d",&c[1],&c[2],&c[3],&c[4],&c[5],&c[6]),
          c[1]||c[2]||c[3]||c[4]||c[5]||c[6])
    {
        sum=0;
        for(int i=1;i<=6;i++)
        {
            sum+=i*c[i];
        }
        printf("Collection #%d:\n",++count);
        if(sum%2==1)
        {
            printf("Can't be divided.\n\n");
            continue;
        }
        sum/=2;
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=6;i++)
        {
            if(i*c[i]>sum)
            {
                for(int j=i;j<=sum;j++)
                    dp[j]=max(dp[j],dp[j-i]+i);
            }
            else
            {
                k=1;
                while(c[i]>k)
                {
                    zero_one(k*i,k*i);
                    c[i]-=k;
                    k*=2;
                }
                zero_one(i*c[i],i*c[i]);
            }
            if(dp[sum]==sum)
            break;
        }
        if(dp[sum]==sum)
            printf("Can be divided.\n");
        else
            printf("Can't be divided.\n");
        printf("\n");
    }
    return 0;
}

网上看到的 用DFS做的:(时间一样,内存少些)
#include <iostream>
using namespace std;
int n[6];
bool flag;
void dfs(int des)
{
    if(des==0)
    {
        flag=true;
        return;
    }
    for(int i=6;i>0;i--)
    {
        if(des>=i&&n[i-1]>0)
        {
            n[i-1]--;
            dfs(des-i);
            if(flag)
            return;
        }
    }
}
int main()
{
    int count=1;
    while(cin>>n[0]>>n[1]>>n[2]>>n[3]>>n[4]>>n[5],
          n[0]||n[1]||n[2]||n[3]||n[4]||n[5])
    {
        int sum=0;
        for(int i=0;i<6;i++)
        sum+=(i+1)*n[i];
        cout<<"Collection #"<<count++<<":"<<endl;
        if(sum%2==1)
        cout<<"Can't be divided."<<endl<<endl;
        else
        {
            int des=sum/2;
            flag=false;
            dfs(des);
            if(flag)
            cout<<"Can be divided."<<endl;
            else
            cout<<"Can't be divided."<<endl;
            cout<<endl;
        }
    }
    return 0;
}

看了下poj的discuss , 发现输入v[i]的时候可以用取余来优化,全部对60取余,或者分别对{ 60,30,20,15,12,10}取余,但貌似是因为数据太弱了才过的,暂时就不想了......discuss里有人暴力过了,我只能说213...

网上有个用数论方法证明做的:
#include <iostream>
using namespace std;

long n[6];
long sum;
const long MAX_N = 60000;

int dividable()
{
    int f[MAX_N];
    for (int i = 0; i <= sum; i++)
        f[i] = 0;
    f[0] = 1;
    for (int i = 0; i < 6; i++)
    {
        for (int j = 1; j <= n[i]; j++)
        {
            int base = j * (i + 1);
            if (base > sum) break;
            for (int k = sum - (i+1); k >= base - i - 1; k--)
                if (f[k])
                    f[k + i + 1] = 1;
            if (f[sum]) return 1;
        }
    }
    return f[sum];
}

int main()
{
    long cases = 0;
    while (true)
    {
        sum = 0;
        for (long i = 0; i < 6; i++)
        {
            cin >> n[i];
        }
        if (n[5] > 5) n[5] = 4 + n[5] % 2;
        if (n[4] > 6) n[4] = 6 - n[4] % 2;
        if (n[3] > 5) n[3] = 4 + n[3] % 2;
        if (n[2] > 5) n[2] = 4 + n[2] % 2;
        if (n[1] > 4) n[1] = 4 - n[1] % 2;
        for (long i = 0; i < 6; i++)
        {
            sum += n[i] * (i + 1);
        }
        if (sum == 0)
            break;
        cases++;
        cout << "Collection #" << cases << ":\n";
        if (sum % 2 != 0)
        {
            cout << "Can't be divided.\n\n";
            continue;
        }
        sum /= 2;
        if (dividable())
            cout << "Can be divided.\n";
        else
            cout << "Can't be divided.\n";
        cout << endl;
    }
    return 0;
}

链接: http://www.blogjava.net/zellux/archive/2007/07/30/133416.html


证明: (暂时懒得看懂)

对于任意一组数,6的个数为n(n>=8)

一、如果可以分成两堆,我们可以分成两种情况:
   1.
      两堆里都有6,那么我们可知:把n改为n-2,仍然可分。
(两堆各减一个6)
2. 只有一堆里有6,设为左边,那么左边的总和不小于6*8=48。
我们观察,5*6=6*5 ,4*3=6*2 , 3*2=6 , 2*3=6 , 1*6=6
而 5*5 + 4*2 + 3*1 + 2*2 + 1*5 = 25 + 8 + 3 + 4 + 5 = 45 < 48
由抽屉原理右边必然存在
(多于5个的5 或者 多于2个的4 或者 多于1个的3
或者 多于2个的2 或者 多于5个的1)
即右边至少存在一组数的和等于若干个6,比如右边有3个4,这样把左边的2个6与右边的3个4交换,则又出现左右都有6的情况。 根据1,我们还是可以把n改为n-2且可分的状态不变。
综合1,2。我们可以看出只要原来n的个数=8,我们就可以把它改为n-2,这样操作一直进行到n<8。我们可以得出结论,对于大于等于8的偶数,可以换成6。
对于大于8的奇数,可以换成7。换完之后仍然可分。

二、如果不能分成两堆:
显然改为n-2时同样也不能分,那么对于大于等于8的偶数,可以换成6;对于大于8的奇数,可以换成7。换完之后仍然不可分。

综合一、二,我们得出结论把不小于8的偶数改为8,大于8的奇数改为7,原来可分与否的性质不会改变。

以上是对6的讨论,同样的方法可以推出
5的个数 6*4 + 4*4 + 3*4 + 2*4 + 1*4 = 64 < 5*13
即5的个数多于12时,偶数换为12,奇数换为11
4的个数 6*1 + 5*3 + 3*3 + 2*1 + 1*3 = 35 < 4*9
即4的个数多于8时,偶数换为8,奇数换为7
3的个数 5*2 + 4*2 + 2*2 + 1*2 = 24 < 3*9
即3的个数多于8时,偶数换为8,奇数换为7
2的个数 5*1 + 3*1 + 1*1 = 9 < 2*5
即2的个数多于4时,偶数换为4,奇数换为3
1的个数 多于5则必然可分(在总数是偶数的前提下)

综上所述,
对于任意一种珠宝的个数n,如果n>=8, 可以将n改写为 11(n为奇数) 或 12(n为偶数)。

进一步分析:
对每个数(1-6),以上只是粗略的估计,可以进一步减少其最大有效取值,例如,
对于6,5*5 + 4*2 + 3*1 + 2*2 + 1*5 = 25 + 8 + 3 + 4 + 5 = 45
就有4和2不能同时出现,5和1不能同时出现,3个5和1个3不能同时出现,4个5不能和1个4同时出现等等,所以组合不出6的整数倍的情况的总价值至多为25,所以当6的个数大于6时,奇数可改为5,偶数可改为6。
1-5 也有类似情况。

为了得出精确值,下面先我们讨论这样一个数论命题。

命题:
可重复的从自然数集中取出n个数(n>=2),其中必有若干个数之和能被n整除。

证明:设取出的n个自然数为a1,a2,a3,.....an

考虑这样的n+1个数 0, a1, a1+a2 , a1+a2+a3 , ...... , a1+a2+a3+...+an, 由于自然数模n的剩余类有n个,所以以上n+1个数中必有两个同余。 这两个数的差必被n整除,而且这两个数的差就是原来的n个数中的一些数的和。
这就证明了命题。

由以上命题
对于6而言,我们至多从{1,2,3,4,5}中可重复的找出5个数使它们不能组合成6的倍数。
所以这些数的和小于等于5*5=25
对于5而言,我们至多从{1,2,3,4,6}中可重复的找出4个数使它们不能组合成5的倍数。
所以这些数的和小于等于6*4=24
对于4而言,我们至多从{1,2,3,5,6}中可重复的找出3个数使它们不能组合成4的倍数。
所以这些数的和小于等于3*6=18 , 然而,两个6就是4的倍数, 所以最多有一个6
此时不能有两个5(2*5+6=16是4的倍数), 最多才6 + 5 + 3 = 14 < 3*5 =15
所以这些数的和小于等于3*5=15
对于3而言,我们至多从{1,2,4,5,6}中可重复的找出2个数使它们不能组合成3的倍数。
所以这些数的和小于等于2*5=10

(6就是3的倍数,所以不能取6)

对于2而言,我们至多从{1,3,4,5,6}中可重复的找出1个数使它们不能组合成6的倍数。

所以这些数的和小于等于1*5=5



考虑到 4*6 < 25 < 5*6 , 我们可以算出6的最大有效个数为5 。

考虑到 4*5 < 24 < 5*5 , 我们可以算出5的最大有效个数为5 。但是其实应该修正为6, 如果遇到如下特殊情况,左边5个6,右边6个5。此时虽然左右可以交换,但是交换后仍然只有一边有5,与(一、2)中讨论情况不符。

考虑到 3*4 < 15 < 4*4 , 我们可以算出5的最大有效个数为4 。但是其实应该修正为5, 如果遇到如下特殊情况,左边4个5,右边5个4。此时虽然左右可以交换,但是交换后仍然只有一边有4,与(一、2)中讨论情况不符。

考虑到 3*3 < 10 < 4*3 , 我们可以算出5的最大有效个数为4 。但是其实应该修正为5, 如果遇到如下特殊情况,左边3个5,右边5个3。此时虽然左右可以交换,但是交换后仍然只有一边有3,与(一、2)中讨论情况不符。

考虑到 2*2 < 5 < 3*2 , 我们可以算出5的最大有效个数为3 。 但是其实应该修正为4,如果遇到如下特殊情况,左边1个3和1个5,右边4个2。此时虽然左右可以交换,但是交换后仍然只有一边有2,与(一、2)中讨论情况不符。


我们得出最后的精确结论:

奇数改为 偶数改为
6的个数大于5 5 4
5的个数大于6 5 6
4的个数大于5 5 4
3的个数大于5 5 4
2的个数大于4 3 4  

ps : 下面评论:“考虑到 2*2 < 5 < 3*2 , 我们可以算出2的最大有效个数为3 。 但是其实应该修正为4,如果遇到如下特殊情况,左边1个3和1个5,右边4个2。此时虽然左右可以交换,但是交换后仍然只有一边有2,与(一、2)中讨论情况不符。” 
如果左边2个5,右边5个2,此时虽然左右可以交换,但是交换后仍然只有一边有2。 
∴2的最大有效个数为什么不修正为5?(暂时不能鉴定正确性,先挂着吧,以后再说)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值