《算法竞赛进阶指南》0x00位运算

常用的位运算操作
#include <bits/stdc++.h>
using namespace std;

int main() {
	int n, k;
	scanf("%d%d", &n, &k);
	
	//约定:整数n的二进制数,由低位 -> 高位依次为:0号位、1号位、2号位... 
	
	//取出k号位上的数字 
	printf("%d\n", (n >> k) & 1);
	
	//取出后k位:和k位1做按位与运算
	printf("%d\n", n & ((1 << k) - 1));
	
	//将k号位取反:k号位和1异或运算;其它位和0异或运算
	printf("%d\n", n ^ (1 << k));
	
	//将k号位赋值为1:k号位和1或运算;其它位和0或运算
	printf("%d\n", n | (1 << k));
	
	//将k号位赋值为0:k号位和0与运算;其它位和1与运算
	printf("%d\n", n & (~ (1 << k)));	 

	//统计非负整数n的二进制中1的个数
	int cnt = 0;
	for (int i = 0; i < 30; i ++) 
		if (n >> i & 1) 
			cnt ++;
	printf("%d\n", cnt);

	//统计非负整数n的二进制中1的个数
	cnt = 0;
	while (n) {
		cnt ++;
		n &= n - 1;
	}
	printf("%d\n", cnt);	

	return 0;
}
a^b
#include <bits/stdc++.h>
#define LL long long
using namespace std;

int main() {
    LL a, b, p;
    scanf("%lld%lld%lld", &a, &b, &p);

    LL res = 1 % p;		//如果b为0, 则结果等于1%p
	//19: 0b10011
    //a^19 = (a^16) * (a^2) * (a^1)
    while (b) {
        if (b & 1) res = res * a % p;
        a = a * a % p;
        b >>= 1;
    }
    printf("%lld\n", res);

    return 0;
}
64位整数乘法
  • 位运算
#include <bits/stdc++.h>
#define LL long long
using namespace std;

int main() {
    LL a, b, p, res = 0;
    scanf("%lld%lld%lld", &a, &b, &p);
	
	//19: 0b10011
    //a*19 = (a*16) + (a*2) + (a*1)
    while (b) {
        if (b & 1) res = (res + a) % p;
        a = a * 2 % p;
        b >>= 1;
    }
    printf("%lld\n", res);

    return 0;
}
  • 扩展:用long double
#include <bits/stdc++.h>
#define LL long long
using namespace std;

int main() {
    LL a, b, p;
    scanf("%lld%lld%lld", &a, &b, &p);

  	//a*b % p = (a%p) * (b%p) % p
	a %= p, b %= p;
	
	// a*b % p = a*b - 下取整(a*b/p) * p
	// a*b/p 的整数部分小于10^18,而long double能提供18~19位有效数字,可以满足
  	//赋值给整型变量,相当于对 a*b/p 取整	
	LL c = (long double) a * b / p;		
	
	//虽然a*b和c*p都可能很大,但是它们的差的范围是[0,p),也就是小于10^18,
  	//虽然a*b和c*p的高位溢出,但是低18位的差被正确保留在long long变量里
	LL res = a * b - c * p;
	
	if (res < 0) res += p;
	else if (res >= p) res -= p;
	
	printf("%lld\n", res);

    return 0;
}
Raising Modulo Numbers
#include <bits/stdc++.h>
#define LL long long
using namespace std;

int main() {
	int z;
	cin >> z;
	
	while (z --) {
		int m, h, ans = 0, res;
		cin >> m >> h;
		for (int i = 1; i <= h; i ++) {
			int a, b;
			cin >> a >> b;
			
			res = 1 % m;
			while (b) {
				if (b & 1) res = (LL)res * a % m;
				a = (LL) a * a % m;
				b >>= 1;
			}
			ans = (ans + res) % m;
		}
		cout << ans << endl;
	}

	return 0;
}
起床困难综合症
  • 暴力30分
#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
int n, m, maxx;
string s[N];
int a[N];

int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i ++) cin >> s[i] >> a[i];
	
	for (int i = 0; i <= m; i ++) {			//枚举初始攻击力0,1,2,...m
		int t = i;
		for (int j = 1; j <= n; j ++) {
			if (s[j] == "AND") t &= a[j];
			if (s[j] == "OR") t |= a[j];
			if (s[j] == "XOR") t ^= a[j];
		}
		maxx = max(maxx, t);
	}
	cout << maxx << endl;

	return 0;
}
  • 位运算100分
#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
int n, m;
string s[N];
int a[N];

int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i ++) cin >> s[i] >> a[i];
	
	//用变量num表示初始攻击力,一开始赋值为0,也就是二进制的每一位都是0 
	int num = 0;
	
	//m的范围是[0,1e9],因为2^30 > 1e9,所以用30位二进制数就够了
	for (int i = 0; i <= 29; i ++) {	
		//num必须小于等于m, 且每一位经过n次位运算之后要尽可能大:
		//如果num的第i位是1,且运算后第i位还是1,则将它赋值为1有助于得到更大的结果 
		//如果num的第i位是1,但运算后第i位变成0,则没必要将它赋值为1
		//如果num的第i位是0,且运算后第i位变成1,则保留初始值0
		//如果num的第i位是0,且运算后第i位变成0,则还是保留初始值0
		//综上,只有当初始为1,且运算后还是1,才有必要将这一位赋值为1
				
		int t = 1;			 
		for (int j = 1; j <= n; j ++) {
			if (s[j] == "AND") t &= (a[j] >> i & 1);	//和第i位运算 
			if (s[j] == "OR")  t |= (a[j] >> i & 1);	//和第i位运算
			if (s[j] == "XOR") t ^= (a[j] >> i & 1);	//和第i位运算
		}
		if (t == 1) num |= (1 << i);					//将第i位赋值为1 
	}
	
	//num应该小于等于m
	for (int i = 29; i >= 0; i --) {
		if (num <= m) break;
		num &= (~(1 << i));								//如果num太大,则将高位赋值为0 
	}
	
	//这时候的num就是符合题意的初始攻击力,经过n次操作后就得到最终的伤害值
	int t = num;
	for (int i = 1; i <= n; i ++) {
		if (s[i] == "AND") t &= a[i];
		if (s[i] == "OR")  t |= a[i];
		if (s[i] == "XOR") t ^= a[i];
	}	
	cout << t << endl;

	return 0;
}
寻找独一无二的数
  • 异或运算的性质有:
    – a ^ a = 0
    – a ^ 0 = a
    – 交换律: a ^ b = b ^ a
    – 结合律: (a ^ b) ^ c = a ^ (b ^ c)
    – 自反:a ^ b ^ a = b
#include <iostream>
#include <cstdio>
using namespace std;

int main() {
	int n, x, ans = 0;
	scanf("%d", &n);
	for (int i = 1; i <= n; i ++) {
		scanf("%d", &x);
		ans ^= x;
	}
	printf("%d", ans);
	
	return 0;
}
连续异或
#include <iostream>
#include <cstdio>
using namespace std;

int main() {
	long long n;
	cin >> n;
	
	if (n % 4 == 1) cout << 1;
	if (n % 4 == 2) cout << n + 1;
	if (n % 4 == 3) cout << 0;
	if (n % 4 == 0) cout << n;
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值