【算法】位运算

文章介绍了位运算的基本概念,包括二进制逻辑左移和右移,以及如何使用位运算交换两个整数。此外,还详细解释了如何通过位运算统计二进制中1的个数。文章提供了一个基于异或的游戏策略问题,讨论了先手和后手的获胜条件,涉及对二进制中1和0的奇偶性分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、位运算

主要参考的这篇文章

1.位运算概述

主要学会这些运算在编程中的编写
请添加图片描述

二进制逻辑左移一位表示乘以2 右移一位除以2

2.位运算的高级操作

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

3.其他操作

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include<math.h>
void swap(int& a, int& b) {
	a ^= b;
	b ^= a;
	a ^= b;
}
int count(int x) {
	int cnt = 0;
	while (x) {
		//如果x是形如aaaa100..的话,x-1就会变成 aaaa0111..
		//从左到右的几个0都会变成1,然后二者进行与运算,那么就会消除掉这个1
		//消除几次1以后x变成0,就说明有几个1
		x = x & (x - 1);
		cnt++;
	}
	return cnt;
}

int main() {
	int a = 2;
	int b = 9;
	int c = 0, d = 0, e = 0;
	//1.位运算交换两个整数
	swap(a, b);
	cout << a << ' ' << b << endl;//输出 9 2
	//2.逻辑左移和逻辑右移
	a = a << 1;// 4*2 = 8
	b = b >> 1;// 2/2 = 1
	cout << a << ' ' << b << endl;//输出 18 1
	//3.或运算 与运算 异或运算
	c = a || b;
	d = a & b;
	e = a ^ b;
	cout << c << ' ' << d << ' ' << e << endl;//输出1 0 19
	//4.判断是奇数还是偶数 取最低位与1相与
	//取末k位,x&(1<<k-1)
	e = e & (1 << 0) & 1;//与1相与
	cout << e << endl;//输出1,说明是最后一位是1,则位奇数

	//5.改变正负性
	a = ~a + 1;//取反是0变1 1变0
	cout << a << endl;//加1得到的才是真正取反的数字
	a = ~a + 1;

	//6.统计二进制位中 1 的个数
	int sum = count(a);
	cout << sum << endl;//18写成二进制数是10010,两个1


	return 0;

}

二、例题——异或运算

题目链接

这里是引用

思路:
异或运算就是 相同得0,不同得1.
0^x = x
1^x = x 如果进行偶数次运算的话会反转成原来的数字
可以根据异或运算的特性,先筛选出特殊情况
> 记住:操作可以选择加在对方身上

  1. 先把所有数字看成二进制,高位用0补齐
  2. 讨论输赢情况
    Alice输入a, 输出A,Bob输入b,输出B,
    如果sum = x1 ^ x2 ^ x3 ^ …^xn
    ①A ^ B = a ^ b ^ sum = 0的话,说明sum = 0 , 则Alice和Bob一定平手
    ②讨论不等于0的情况,不等于0的话就从最高位开始向下计算1的个数,并且存到num[i]中,num[i]表示第i位有多少个1
    num[i] = 1时,先手必赢
    num[i] = 偶数,接着考虑下一位
    num[i] > 1 且为奇数时,0的个数为偶数的话,先手赢,输出1
    num[i] > 1 且为奇数时,0的个数为奇数的话,后手赢,输出-1

因为如果选择0相当于轮空,没有变化。
因此要争抢1,奇数个1的话,偶数个0的话,先手赢。
1和0的个数都为奇数的话,二者都要争抢0,让最后一个1到自己手中。假如Alice先抢0,最后一个1在bob手中,它可以加到alice身上,如果Alice先抢1的话,Bob也可以加在alice身上,无论如何都是输

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//根据题目,初始值都为0
//如果只有一个数的话,表明只有一局,只有一局就是谁先手谁胜或者平手(是0),这是特殊情况
long long cnt0, cnt1;
long long sum;
long long a[200020];
//判断胜负
int judge() {
    //偶数个1,继续判断
    if (cnt1 % 2 == 0)
        return 0;

    if (cnt1 == 1) {//1个1先手必胜
        return 1;
    }
    //奇数个0,1,后手必赢
    if (cnt1 % 2 != 0 && cnt0 % 2 != 0) {
        return -1;
    }
    else
        return 1;
}
//统计0,1个数
void solve(int n) {
    sum = 0;
    for (int i = 1; i <= n; i++) {
        //scanf %d 后面不要添加任何东西
        //scanf("%lld", &a[i]);//一局询问次数
        cin >> a[i];
        sum ^= a[i];
    }
    //平局
    if (sum == 0) {
        //printf("0\n");
        cout << 0 << endl;
        return;
    }
    //高位开始枚举,获取0和1的个数
    for (int i = 20; i >= 0; i--) {//最高位
        cnt0 = cnt1 = 0;
        //这个for循环内计数,一直循环完一组数据的第一位
        for (int j = 1; j <= n; j++) {
            //逻辑右移
           // long long x = a[j];
            if ((a[j] >> i) & 1) {//不用while循环是因为这只是一层循环,判断完第一个数的第一位,要去判断第二个数的第一位
                //当前层全部判断完成后就判断是不是可以分出胜负,不可以的话再判断下一位
                cnt1++;
                //x >>= 1;//表示是x等于逻辑右移后的一位
            }
            else {
                cnt0++;
            }
        }
        int res = judge();
        if (res == 0) {
            continue;
        }
        else {
            //printf("%lld\n", res);
            cout << res << endl;
            return;
        }
    }
}


int main() {
    long long t;
    long long n;
    //scanf("%lld", &t);//t局
    cin >> t;
    while (t--) {
        //scanf("%lld", &n);//数列长度
        cin >> n;
        solve(n);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值