OJ:http://acm.lsnu.edu.cn/oj/problem.php?id=2400
算法的主要思路是01背包。
有n个糖果,每个糖果都有个重量值,要把平分糖果分给两个人,两个人分得的重量要相等且要尽量的大!多余的糖果可以送出去,注意糖果不能被切开。
因为要把多余的糖果送出去,所以我们可以枚举送出去的糖果,然后剩下的糖果如果能被两个人平分的话,这就是一组答案。
然后记录下送出去的糖果重量的最小的那一个就可以了。
那么问题来了,如何求出剩下的糖果是否能被平分呢?这就需要用到01背包了。01背包的核心思想是一个物品取或者不取,利用这种思想就能知道能不能被平分了。
我们本来是利用01背包求出某些物品在容量一定情况下能装下的物品的最大值,但是我们现在使用的时候,就将背包的容量设置为我们剩余的所有糖果的重量(注意:现在背包的容量是等于所有糖果重量之和的,且背包内的糖果的价值和糖果所占空间是一样的)。如果在背包容量使用了1/2的时候,其装下的糖果的重量刚好是所有糖果的一半,说明剩下的糖果能被平分。
因为在平分的时候两个人不可能同时拥有一个糖果,就是说一个糖果只能使用一次,刚好01背包能满足这个条件
代码:
#include <stdio.h>
#include <stdlib.h>
#define min(x,y) x < y ? x : y
#define max(x,y) x > y ? x : y
#define MAX 21
// 糖果的重量
int nums[MAX];
// 标识哪些重量值能由糖果组合出来
int sum[20010];
// 临时数组
int t[MAX];
int n;
// 所有糖果重量的和
int maxSum;
int main()
{
for(;scanf("%d",&n) && n!=0;){
maxSum = 0;
for(int i=0;i<n;i++){
scanf("%d",&nums[i]);
maxSum += nums[i];
}
int ans = maxSum;
// 枚举送出去的所有不同的糖果组合
for(int i=0;i<(1<<n);i++){
// 送出的糖果重量
int reduce = 0;
int k = 0;
// 把送出去的糖果排除出去
for(int j=0;j<n;j++){
if(i&(1<<j)){
reduce += nums[j];
}else{
t[k++] = nums[j];
}
}
// 如果糖果总重量减去送出去的剩下的重量数是奇数,肯定不能被划分
if((maxSum - reduce)&1){
continue;
}
// 如果当前这次送出去的重量比之前得到的最小值更多,就不用算了
if(reduce >= ans){
continue;
}
// 使用01背包求出剩下的这些数能组合出哪些重量
dp(maxSum - reduce,k);
// 如果背包的空间的一半和装下的糖果的重量刚好相等,说明剩下的糖果能被平分
if(sum[(maxSum-reduce)>>1] == (maxSum-reduce)>>1){
ans = min(ans,reduce);
}
}
printf("%d\n",ans);
}
}
// 01背包,s是背包的容量,count是物品的个数
// 需要注意的是,这里的背包的大小,是所有物品的总和,所以说这里的01背包与常规的01背包还是有区别的
// 常规的01背包求解的是在有限的空间下使物品价值最大,但是这里的空间是肯定能放下所有物品的
// 所以这里的01背包求出来的结果,其实就是看这些糖果能组合出哪些重量值
// 如果使用了重量为n的糖果使用了n个空间,说明可以组合出n这个重量
void dp(int s,int count){
memset(sum,0,sizeof(sum));
for(int i=0;i<count;i++){
for(int j=s;j>=t[i];j--){
// j是反向向前的,为了重用上一行的数据同时也避免了一个糖果被重复使用
// 这也是01背包与完全背包的区别
sum[j] = max(sum[j],sum[j-t[i]]+t[i]);
}
}
}