牛客小白月赛94--小苯的01背包(easy&hard ~包会的)

1.题目

在这里插入图片描述
困难版的输入:
困难版的输入

输入样例:

3 1
7 3
10 7
9 6

输出样例:

2

在这里插入图片描述
注:本题的easy(简单版),与hard(困难版)唯一的不同之处只有数据范围。

2.解题思路+代码:

我们可以发现本题虽然和01背包很像,但是却不能用01背包来做,这个题只能枚举答案,hard版本质是贪心,依次枚举判断高位的情况。

当然也可以直接干hard版,不过easy版的思路还是很重要,因为很多时候我们就是要枚举答案,枚举答案就是正解,比如二分枚举答案等,不过这题是不能二分的,没有单调性

easy版:

思路:

我们可以发现最大价值之有2000,我们可以从后往前枚举,当有一个符合而条件,输出即可。
难点是如何判断该价值是否符合条件。
这里我们首先定义了一个体积,是1<<11,因为log(2000)就是10.多所以至少11位。当然比11大也没问题,因为第一次&就会变成你所选第一个物品的体积(具体原因同下,和&运算有关)

然后我们从1-n依次枚举每个物品,判断条件: (w[j]&i) == i,试想起初i(价值)是比较大的,那么&上一个小的,必然不是小的,跳过,只有在遇上>=所枚举的价值的时候才会成立(因为&上一个数要么变小,要么不变), 因此我们是&上了每个符合条件的大于等于当前价值的物品,这样既能使物品价值不变,还能压缩物品体积。因此我们也可以看出,最大可能的价值就是,max(w[i]),因此也不一定非从2000开始,从max(w[i])开始可以过hard更多数据。

对于大数而言,例如2,&完以后是会发生变化的,因此大的数我们还得判断是否符合条件,即让当前枚举价值不变。
在这里插入图片描述

代码:

#include<iostream>
using namespace std;

const int N=2e3+10;
int v[N],w[N];

int main()
{
    int n,k;
    cin>>n>>k;
    for(int i=1;i<=n;i++)
    {
        cin>>v[i]>>w[i];
    }

    int ans=0;
    for(int i=2000;i>=0;i--)
    {
        int v_sum=(1<<11)-1;//至少右移11位,解释见上
        for(int j=1;j<=n;j++)
        {
            if( (w[j]&i) == i) v_sum&=v[j];
            //如果最大值不到i,&下来绝对不等于i
            //肯定是按价值由大到小开始枚举,然后判断&完价值是否不变,来压缩体积
        }
        if(v_sum<=k||i==0)//答案有可能是0,到0了就输出就行
        {cout<<i;return 0;}
    }

    return 0;
}

hard版:

如果用上面的枚举过了83%的数据,对于蓝桥杯啥的肯定够用了。下面来说一种位运算优化版本的。

思路:

hard版的数据范围来到了10^9,log (10 ^ 9)大概是29.8,至少<<30位。当然多了肯定没问题,&一下就没了。

价值最大是10^9,又是&操作,因此我们用int完全可以,不一定是hard就开long long。

这题和上次不同,我们采取从高位开始利用二进制枚举答案的每一位。

我们首先value采用的是 | 操作,即除了当前位置,不会改变ans的其他位置的值。

当我们没有枚举到合适高位的时候,其实是这样的过程:
在这里插入图片描述
我们上面说过,&比当前价值大的可以,因此当我们100…00全是0都不可以,那么后面有1的就更不行了(因为价值更大了),而easy版的枚举需要枚举每个位置的状态,hard也就是在这里做出了优化

当我们枚举到一个可以的高位的时候,我们会把它记录到ans中,因为每次是|操作,所以我们能把高位保留下来,继续去判断下面的高位行不行。

代码:

#include<iostream>
using namespace std;

const int N=2e5+10;
int v[N],w[N];

int main()
{
    int n,k;
    cin>>n>>k;
    
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    
    int ans=0;
    for(int i=30;i>=0;i--) //log(10^9)=29.8...  所以至少取30
    {
        int value=ans|(1<<i);
        int v_sum=(1<<31)-1;
        for(int j=1;j<=n;j++)
        {
            if( (w[j]&value) == value ) v_sum &= v[j];
        }
        if(v_sum<=k) ans=value;
    }
    
    cout<<ans;
    return 0;
}

呜呜呜,看了两三个小时才看懂,给个赞叭~ ~ ~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值