把总长度求出来,然后用和背包问题类似的dp算法求是否可以达到这个总长度的一半,但是超时了。
找到一个优化方案,结果0ms就过了,orz
http://162.105.81.202/course/problemSolving/dividingProve.doc
以下论证均转自这篇文章。
结论 对于任意一种珠宝的个数n,如果n>=8, 可以将n改写为 11(n为奇数) 或 12(n为偶数)。
证明:
对于任意一组数,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
优化后的代码:
找到一个优化方案,结果0ms就过了,orz
http://162.105.81.202/course/problemSolving/dividingProve.doc
以下论证均转自这篇文章。
结论 对于任意一种珠宝的个数n,如果n>=8, 可以将n改写为 11(n为奇数) 或 12(n为偶数)。
证明:
对于任意一组数,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
优化后的代码:
#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 ;
}
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 ;
}