c++算法基础必刷题目——按位贪心

按位贪心

1、毒瘤xor

NC18979 毒瘤xor

题目描述

小a有N个数a1, a2, …, aN,给出q个询问,每次询问给出区间[L, R],现在请你找到一个数X,使得
1、0⩽X<231
2、 ∑ i = L R \sum_{i=L}^R i=LRX⊕a[i]最大,⊕表示异或操作(不懂的请自行百度)

输入描述:

第一行一个整数N,表示序列的长度
第二行N个整数,表示序列内的元素
第三行一个整数q,表示询问的个数
接下来q行,每行两个整数[L, R],表示询问的区间

输出描述:

输出q行,每行一个整数表示答案
若有多组可行解,请输出较小的解

示例1

输入
5
4 78 12 1 3
3
2 5
1 4
3 3

输出
2147483632
2147483635
2147483635

备注:

对于30%的数据,n , q ≤ 10
对于60%的数据,n , q ≤ 1000
对于100%的数据,n, q ≤ 105
保证ai < 231

解题思路:
1、按二进制位来分析问题,统计区间中每一位1的数量,那么0的数量可以间接求出来,如果0的数量比1的数量多,那么该位就应该是1,否则就应该是0,下面来解释样例

2、2到5有4个数78、12、1、3,二进制分别如下
78 :0100 1110
12 :0000 1100
1 :0000 0001
3 :0000 0011

前面24位全0省略了,那么一共有4个数,那么当0的个数大于等于3时该位是1,那么X应该是111 1111 1111 1111 1111 1111 1111 0000,即2147483632

代码:

#include<bits/stdc++.h>
using namespace std;
int main(){
    int n;
    cin>>n;
    int a[100010]={0};
    int b[100010][32]={0};
    for(int i=1;i<=n;i++){
        cin>>a[i];
        for(int j=0;j<31;j++){//统计每一位的1的个数,进行前缀和
            b[i][j]+=b[i-1][j]+((a[i]>>j)&1);
        }
    }
    int m;
    cin>>m;
    for(int i=0;i<m;i++){
        int l,r;
        cin>>l>>r;
        int ans=0;
        for(int j=0;j<31;j++){
            //使用差分快速查询每一位区间的和,如果1的数量小于0的数量,那么该位就为0
            if((b[r][j]-b[l-1][j])*2<(r-l+1)){
                ans+=(1<<j);
            }
        }
        cout<<ans<<endl;
    }
}

2、兔子的区间密码

NC20860 兔子的区间密码

题目描述

  有一只可爱的兔子被困在了密室了,密室里有两个数字,还有一行字:
只有解开密码,才能够出去。
  可爱的兔子摸索了好久,发现密室里的两个数字是表示的是一个区间[L,R]
而密码是这个区间中任意选择两个(可以相同的)整数后异或的最大值。
比如给了区间[2,5] 那么就有2 3 4 5这些数,其中 2 xor 5=7最大 所以密码就是7。
兔子立马解开了密室的门,发现门外还是一个门,而且数字越来越大,兔子没有办法了,所以来求助你。
  提示:异或指在二进制下一位位比较,相同则 0 不同则 1
  例如2=(010)2,5=(101)2
  所以2 xor 5=(111)2=7

输入描述:

第一行一个数 T,表示数据组数。
接下来 T 行,每行两个数 L,R, 表示区间[L,R]。

输出描述:

输出共T行每行一个整数,表示[L,R]的密码。

示例1

输入
5
1 10
2 3
3 4
5 5
2 5

输出
15
1
7
0
7

备注:

对于30%的数据
1 ≤ T ≤ 10
0 ≤ L ≤ R ≤ 100
对于另外10%的数据
L=R
对于70%的数据
1 ≤ T ≤ 10
0 ≤ L ≤ R ≤ 50000
对于100%的数据
1 ≤ T ≤ 10000
0 ≤ L ≤ R ≤ 1018
(对于100%的数据) 输入数据较大,请使用快速读入。

解题思路:
下面来解释样例
1、1和10,1的二进制是0000 0001,10的二进制是0000 1010,找到第一位不同的,那就是第5位,那么选择的第一个数就是0000 1000,第二个数是0000 0111,答案是0000 1111,即15

2、2和3,2的二进制是0000 0010,3的二进制是0000 0011,找到第一位不同的,那就是第8位,那么选择的第一个数就是0000 0011,第二个数是0000 0010,答案是0000 0001,即1

3、3和4,3的二进制是0000 0011,4的二进制是0000 0100,找到第一位不同的,那就是第6位,那么选择的第一个数就是0000 0100,第二个数是0000 0011,答案是0000 0111,即7

4、相信您看出来规律了吧!答案一定是2的幂次方-1。
代码:

#include<bits/stdc++.h>
using namespace std;
int main(){
    int t;
    cin>>t;
    while(t--){
        long long l,r;
        long long ans=0;
        cin>>l>>r;
        for(int i=62;i>=0;i--){
            if(((l>>i)&1)!=((r>>i)&1)){//出现了不同的位
                ans=((1ll<<(i+1))-1);//该位后全1
                break;
            }
        }
        cout<<ans<<endl;
    }
}

3、起床困难综合症

NC17857 起床困难综合症

题目描述

  21 世纪,许多人得了一种奇怪的病:起床困难综合症,其临床表现为:起床难,起床后精神不佳。作为一名青春阳光好少年,atm 一直坚持与起床困难综合症作斗争。通过研究相关文献,他找到了该病的发病原因:在深邃的太平洋海底中,出现了一条名为 drd 的巨龙,它掌握着睡眠之精髓,能随意延长大家的睡眠时间。正是由于 drd 的活动,起床困难综合症愈演愈烈,以惊人的速度在世界上传播。为了彻底消灭这种病,atm 决定前往海底,消灭这条恶龙。
  历经千辛万苦,atm 终于来到了 drd 所在的地方,准备与其展开艰苦卓绝的战斗。drd 有着十分特殊的技能,他的防御战线能够使用一定的运算来改变他受到的伤害。具体说来,drd 的防御战线由 𝑛 扇防御门组成。每扇防御门包括一个运算 op 和一个参数 𝑡,其中运算一定是 OR,XOR,AND 中的一种,参数则一定为非负整数。如果还未通过防御门时攻击力为 𝑥 ,则其通过这扇防御门后攻击力将变为 𝑥 op 𝑡 。最终drd 受到的伤害为对方初始攻击力 𝑥 依次经过所有 𝒏 扇防御门后转变得到的攻击力。
  由于atm 水平有限,他的初始攻击力只能为 0 到 𝑚 之间的一个整数(即他的初始攻击力只能在 0, 1, … , 𝑚 中任选,但在通过防御门之后的攻击力不受 𝑚 的限制)。为了节省体力,他希望通过选择合适的初始攻击力使得他的攻击能让 drd受到最大的伤害,请你帮他计算一下,他的一次攻击最多能使 drd 受到多少伤害。

输入描述:

第 1 行包含2 个整数,依次为 𝑛, 𝑚 ,表示drd 有 𝑛 扇防御门,atm 的初始攻击力为 0 到 𝑚 之间的整数。
接下来 𝑛 行,依次表示每一扇防御门。每行包括一个字符串 op 和一个非负整数 𝑡,两者由一个空格隔开,且 op 在前, 𝑡 在后,op 表示该防御门所对应的操作,𝑡 表示对应的参数。

输出描述:

输出一行一个整数,表示atm 的一次攻击最多使 drd 受到多少伤害。

示例1

输入
3 10
AND 5
OR 6
XOR 7

输出
1

说明

atm 可以选择的初始攻击力为 0,1, … ,10。
假设初始攻击力为 4,最终攻击力经过了如下计算
4 AND 5 = 4
4 OR 6 = 6
6 XOR 7 = 1
类似的,我们可以计算出初始攻击力为 1,3,5,7,9 时最终攻击力为 0,初始攻击力为 0,2,4,6,8,10 时最终攻击力为 1,因此atm 的一次攻击最多使 drd 受到的伤害值为 1。

备注:

在这里插入图片描述

解题思路:

1、按位贪心,越高位比重越高,我们应该从最高位开始,向最低位逐步分析问题

2、因为按位与、按位或、按位异或都是不会产生进位的,所以我们可以按位独立去考虑问题,某位不是1就是0,那么我们可以让那一位是1和0,查看结果再做判断,例如某一位是1时,经过一系列操作变成了0,是0时,经过一系列操作变成了1,那么就让该位是0是最合适的

3、从高位到低位贪心,如果该位能变1,那么在符合要求的情况下一定要令其为1

代码:

#include<bits/stdc++.h>
using namespace std;
int main(){
    int n,m;
    cin>>n>>m;
    int t=0;
    int ans=0;
    int ma=(1ll<<31)-1;//全1
    int mi=0;//全0
    for(int i=0;i<n;i++){
        string s;
        int val;
        cin>>s>>val;
        if(s=="AND"){
            ma&=val;
            mi&=val;
        }else if(s=="OR"){
            ma|=val;
            mi|=val;
        }else{
            ma^=val;
            mi^=val;
        }
    }
    for(int i=30;i>=0;i--){
        if((mi>>i)&1){//如果该位是0,最后变1了,那么就让这位是0
            ans+=(1<<i);
            continue;
        }else if(((ma>>i)&1)&&t+(1<<i)<=m){//如果该位是1,最后还是1,那么在符合条件的情况下让该位是1
            t+=(1<<i);
            ans+=(1<<i);
        }
    }
    cout<<ans<<endl;
}

是不是很简单呢?

刚接触肯定会觉得难,多些做题多些用,熟悉了就容易了,兄弟萌,加油!!!

文章尚有不足,感谢您的指正

感谢观看,点个赞吧

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

旧林墨烟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值