第十二届蓝桥杯B组省赛砝码称重题解

请添加图片描述
请添加图片描述

方法一

题目分析

题目有点像01背包,所以尝试使用动态规划来解决。
将砝码一个个放入时,用数组F[i,w]存放命题"前i个砝码可称出重为w的物品"的真假性,简称存放可行性
最后遍历F[n,w]即可数出可称出物品的数量。

数据结构

用W[i]存放第i个砝码的重量(下文中第i个砝码与W[i]是一个意思)
F[i,j]用来动态规划

状态转移方程的设计

虽然天平两边都能放东西考虑起来不容易,但如果先规定砝码只能放在右边问题就会简单很多,然后尝试用只右边的情况导出两边都放的情况。

只右边

前i-1个能称出重量为w的物体,或者能称出重量为w-W[i]的物体注:要先把F[0,0]置为真(显然对),但数答案时不数(题目只要正的)
F[i,w]=F[i-1,w]|F[i-1,w-W[i]]

双边

前i个能称出重量为w的物体,那把重为M的一堆砝码从右边移动到左边,还能称出w-2M的物品。
那怎么知道右边有没有一堆恰好重量为M的砝码?
由只右边的状态转移方程可以知道
如果F[i,w]==F[i,w-W[i]]==1,那肯定有一种情况满足如下要求
1、能称出w
2、右边有w[i]
转移方程出来了
if(F[i,j]&F[i,j-W[i]])F[i,j-2*W[i]]=F[i,j]
运气很好,每跑完一趟双边,只右边的性质还会保留,所以用同一个F。
(大概是这样,可以意会挺难言传,一会看代码就明白了)

空间优化

因为F[i,w]只与F[i-1,x]和F[i,y]有关(x,y<w)
所以能直接把i这个维度压缩掉(用新数据覆盖旧数据)。

总结

N为砝码数,S为砝码总重
我们获得了一个时间复杂度O(NS),空间复杂度O(n)的算法
推荐看背包问题九讲2.0-崔添翼,讲的很好。

通关代码

#include<iostream>
#define NMAX 100
#define WMAX 100000
using namespace std;
int W[NMAX+1];
int F[WMAX+1];
int main(){
    int N,S=0,ans=0;
    cin >> N;
    for(int i=1;i<=N;++i){
        cin >> W[i];
        S+=W[i];
    }
    F[0]=1;
    //只右边
    for(int i=1;i<=N;++i){//砝码一个一个放
    	//第二层循环的方向是压缩空间的关键,仔细思考
        for(int j=S;j>=W[i];--j){//获取可行性
            F[j]=F[j]|F[j-W[i]];
        }
    }
    //两边
    for(int i=1;i<=N;++i){//砝码一个一个尝试着往左移动
    	//第二层循环的方向关乎算法的正确性,仔细思考
        for(int j=2*W[i];j<=S;++j){
            if(F[j]&F[j-W[i]]){//如果右边有W[i]
                F[j-2*W[i]]=F[j];//放到左边去
            }
        }
        //因为砝码移动后可以称出轻的物品,所以第二层要从轻到重
        //从重到轻会影响后面的判断
    }
    for(int i=1;i<=S;++i){
        if(F[i])ans++;    
    }
    cout << ans << endl;
    return 0;
}

方法二

题目分析

砝码可以放在两边意味着01背包成了-101背包
这是很容易转化为01背包的(正向平移),
最后截取>S的答案即可。

#include<iostream>
#define NMAX 100
#define WMAX 100000
using namespace std;
int W[NMAX+1];
int F[2*WMAX+2];
int main(){
    int N,S=0,ans=0;
    cin >> N;
    for(int i=1;i<=N;++i){
        cin >> W[i];
        S+=W[i];
    }
    F[0]=1;
    S*=2;//把负的移动到正的
    for(int i=1;i<=N;++i){//转化为01背包
        for(int j=S;j>=W[i];--j){
            F[j]=F[j]|F[j-W[i]];
        }
        for(int j=S;j>=W[i];--j){
            F[j]=F[j]|F[j-W[i]];
        }
    }
    for(int i=S/2+1;i<=S;++i){
        if(F[i]){
            ans++;
        }
    }
    cout << ans << endl;
    return 0;
}

方法二更容易理解,也更好实现,时空复杂度上也只有系数的增加。
当然还有其它方法,去搜搜别人的博客吧。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值