分糖果 01背包

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]);
        }
    }
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值