找零钱问题

来源:互联网 发布:fins默认端口号是多少 编辑:程序博客网 时间:2024/04/29 12:34
如题,给出6种珠宝,每种珠宝的价值为1,2,3,4,5,6;每种珠宝的数量为m1,m2,m3,m4,m5,m6;求解:是否这一堆珠宝集合可以被平分为价值相同的两堆?

问题可以被转化为,num为这堆珠宝总价值的一半,问是否有一定数量珠宝的价值恰好等于num?这样问题就转化为找零钱问题,用动态规划可解;
算法:bool judge[60001];judge[i]用来判断价值i是否能够用来表示;
for(i=1;i<=6;i++)
{
for(j=total;j>=0;j--)//注意,从大到小进行遍历
{
if(judge[j]==1)
{
for(k=1;k<=marble[i];k++)
{
m=j+i*k;
if(m==num){b=1;goto end;}
else if(m>num)break;
else
{
if(judge[m]==1)break; //注意此处优化
else
{
judge[m]=1;
if(total<m)total=m;
}
}
}
}
}
}

另外本题还有一个重要的优化:


对于任意一组数,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的偶数改为6,大于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为偶数)。

这样就极大的减小了题的规模,用搜索或者dp都能完成。