题目链接:
https://vjudge.net/contest/348156#problem/L
题面:
翻译:
玛莎和比尔拥有一批大理石。他们想把收藏品分成两半,以便双方都能得到同等份额的大理石。如果所有的弹珠都有相同的价值,这很容易,因为这样他们就可以把收藏分成两半。但不幸的是,有些弹珠比其他弹珠更大,或者更漂亮。因此,Marsha和Bill首先为每个大理石指定一个值,一个介于1到6之间的自然数。现在他们想把弹珠分开,这样每个弹珠的总价值都是一样的。
不幸的是,他们意识到用这种方法划分弹珠是不可能的(即使所有弹珠的总价值是相等的)。例如,如果有一个值为1的大理石、一个值为3的大理石和两个值为4的大理石,则不能将它们分割为一组相等的值。所以,他们要求你写一个程序来检查弹珠是否有一个公平的分区。
输入:
输入中的每一行描述一个要分割的大理石集合。这些行由六个非负整数n1、n2、…、n6组成,其中ni是值i的弹珠数。因此,上面的示例将由输入行“1 0 1 2 0”描述。最大弹丸总数为20000枚。
输入文件的最后一行将是“0 0 0 0 0”;不要处理这一行。
输出:
对于每个Collection,输出“Collection #k:”,其中k是测试用例的编号,然后是“Can be divided.”或“Can’t be divided.”。
在每个测试用例之后输出一个空行
思路:
这道题目其实题目意思有点子绕,其实这道题目就是有6中弹珠,每种弹珠对应的价值为1,2,3,4,5,6,然后我们要输入的是每种弹珠的数目,我们需要判断的是这些弹珠能不能正好分成两个平分的分块,所以我们首先需要判断总价格是否能够被2整除,如果不能的话,那么就肯定不存在一个平分的分块了,如果可以被2整除的话,那么接下来就成了一个多重背包的问题,因为题目数据给的比较大,所以我们需要使用二进制转换来优化程序的计算量,减少计算的时间,经过二进制转换之后就成了基本的01背包的问题,然后我们就只需要判断是否存在一个状态满足当前的价格正好等于总价格的一半,就证明存在一个平分的分块,如果到最后都没有出现的,就证明不存在一个平分的分块了。
参考代码:
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
int dp[120000],b[12000];
int main()
{
int a[6],x=0;
while(1)//实现多组输入
{
x++;//用来控制为第几个测试样例的编号
int flag=0;
int sum=0,m=0;
if(x!=1)
printf("\n");
for(int i=1; i<=6; i++)
{
scanf("%d",&a[i]);
sum+=a[i];//计算是否每个个数的之和
m+=i*a[i];//计算总的价值之和
}
if(sum==0)//如果都为0则直接跳出循环
break;
if(m%2==0)//如果总的价值能够被2整除的话才可以实现平分
{
int s=0;
for(int i=1; i<=6; i++)
{
for(int k=1; k<=a[i]; k*=2)
{
b[s++]=k*i;
a[i]-=k;
}
if(a[i]>0)
{
b[s++]=a[i]*i;
}
}//上面这两个for循环是二进制优化,可以减少计算量
memset(dp,0,sizeof(dp));
m=m/2;//把总的价格直接减半
for(int i=0; i<s; i++)
{
for(int j=m; j>=b[i]; j--)
{
dp[j]=max(dp[j],dp[j-b[i]]+b[i]);//把多重背包转换为01背包的问题
if(dp[j]==m)//如果满足一个特定的值等于m的话,就将其进行标记,一旦重新等于一半的话,就证明可以平分
{
flag=1;
break;
}
}
}
}
if(flag==1)//如果flag被标记了就可以平分,所以就输出Can be divided.
printf("Collection #%d:\nCan be divided.\n",x);
else
printf("Collection #%d:\nCan't be divided.\n",x);
}
return 0;
}