赞
踩
位与运算将两个操作数的对应位进行与操作,如果两个位都为 1,则结果位为 1,否则为 0。
int a = 5; // 0101
int b = 3; // 0011
int result = a & b; // 0001,结果为 1
位或运算将两个操作数的对应位进行或操作,如果两个位中至少有一个为 1,则结果位为 1,否则为 0。
int c = 5; // 0101
int d = 3; // 0011
int result = c | d; // 0111,结果为 7
位异或运算将两个操作数的对应位进行异或操作,如果两个位不同,则结果位为 1,否则为 0。
int e = 5; // 0101
int f = 3; // 0011
int result = e ^ f; // 0110,结果为 6
位取反运算将操作数的每一位取反,即 1 变为 0,0 变为 1。
int g = 5; // 0101
int result = ~g; // 1010,结果为 -6(考虑符号位)
左移运算将操作数的所有位向左移动指定的位数,右侧补 0。
int h = 5; // 0101
int result = h << 2; // 010100,结果为 20
右移运算将操作数的所有位向右移动指定的位数,对于无符号数,左侧补 0;对于有符号数,左侧补符号位。
int i = 20; // 10100
int result = i >> 2; // 00101,结果为 5
通过位与、位或和位异或,可以方便地设置、清除或检查一个整数中的特定二进制位。
int num = 0x12; // 0001 0010
// 设置第 3 位
num |= (1 << 3); // 0001 1010
// 清除第 2 位
num &= ~(1 << 2); // 0001 0010
// 检查第 4 位是否为 1
bool isSet = (num & (1 << 4))!= 0;
在某些情况下,可以使用位运算来有效地压缩数据,减少存储空间的使用。
例如,用一个字节表示 8 个布尔值。
char flags = 0;
flags |= (1 << 0); // 设置第 0 个布尔值为真
flags &= ~(1 << 1); // 设置第 1 个布尔值为假
通过左移和右移可以实现快速的乘以 2 的幂和除以 2 的幂的运算。
int num = 5;
int multiplyBy8 = num << 3; // 相当于乘以 8
int divideBy4 = num >> 2; // 相当于除以 4
在系统编程和权限管理中,常常使用位运算来表示和处理各种权限和标志。
enum Permissions {
READ = 1 << 0,
WRITE = 1 << 1,
EXECUTE = 1 << 2
};
int userPermissions = READ | WRITE; // 用户具有读和写权限
enum OptionFlags {
OPTION_1 = 1 << 0,
OPTION_2 = 1 << 1,
OPTION_3 = 1 << 2
};
void handleOptions(int options) {
if (options & OPTION_1) {
// 处理 OPTION_1 相关逻辑
}
if (options & OPTION_2) {
// 处理 OPTION_2 相关逻辑
}
// 以此类推
}
位运算通常比普通的算术运算更高效,因为它们直接在硬件层面上操作二进制位,不需要进行复杂的数学计算。
例如,在处理大量数据或者对性能要求苛刻的场景中,使用位运算可以显著提高程序的执行速度。
// 比较使用乘法和左移的性能 #include <iostream> #include <chrono> void multiply(int n, int times) { for (int i = 0; i < n; ++i) { int result = i * times; } } void shiftLeft(int n, int times) { for (int i = 0; i < n; ++i) { int result = i << times; } } int main() { int n = 10000000; int times = 4; auto start1 = std::chrono::high_resolution_clock::now(); multiply(n, times); auto end1 = std::chrono::high_resolution_clock::now(); std::chrono::duration<double> elapsed1 = end1 - start1; auto start2 = std::chrono::high_resolution_clock::now(); shiftLeft(n, times); auto end2 = std::chrono::high_resolution_clock::now(); std::chrono::duration<double> elapsed2 = end2 - start2; std::cout << "乘法运算耗时: " << elapsed1.count() << " 秒" << std::endl; std::cout << "左移运算耗时: " << elapsed2.count() << " 秒" << std::endl; return 0; }
在有符号数的右移操作中,要注意符号扩展可能导致的结果不一致。
int signedNum = -5;
int shiftedSigned = signedNum >> 1; // 结果取决于编译器的实现,可能不是预期的
不同的硬件平台和编译器可能对位运算的处理方式略有不同,尤其是涉及到有符号数的操作。
位运算虽然高效,但可能会降低代码的可读性。在使用时,应添加足够的注释以说明其目的和逻辑。
位运算在 C++ 中是一种强大而灵活的工具,掌握它可以让我们在编程中更加高效地处理数据、优化性能,并实现一些复杂而有趣的功能。但同时,我们也要注意其使用的场景和可能带来的潜在问题,以确保代码的正确性和可维护性。
希望通过这篇博客,您对位运算在 C++ 中的应用有了更深入的理解和认识,能够在实际编程中灵活运用,创造出更优秀的程序。
给出一个小于 2 32 2^{32} 232 的非负整数。这个数可以用一个 32 32 32 位的二进制数表示(不足 32 32 32 位用 0 0 0 补足)。我们称这个二进制数的前 16 16 16 位为“高位”,后 16 16 16 位为“低位”。将它的高低位交换,我们可以得到一个新的数。试问这个新的数是多少(用十进制表示)。
例如,数 1314520 1314520 1314520 用二进制表示为 0000 0000 0001 0100 0000 1110 1101 1000 0000\,0000\,0001\,0100\,0000\,1110\,1101\,1000 00000000000101000000111011011000(添加了 11 11 11 个前导 0 0 0 补足为 32 32 32 位),其中前 16 16 16 位为高位,即 0000 0000 0001 0100 0000\,0000\,0001\,0100 0000000000010100;后 16 16 16 位为低位,即 0000 1110 1101 1000 0000\,1110\,1101\,1000 0000111011011000。将它的高低位进行交换,我们得到了一个新的二进制数 0000 1110 1101 1000 0000 0000 0001 0100 0000\,1110\,1101\,1000\,0000\,0000\,0001\,0100 00001110110110000000000000010100。它即是十进制的 249036820 249036820 249036820。
一个小于 2 32 2^{32} 232 的非负整数
将新的数输出
1314520
249036820
就是一个简单的位移运算
#include <iostream>
using namespace std;
unsigned int n;
int main()
{
cin>>n;
cout<<(n >> 16) + (n << 16)<<endl;
return 0;
}
id: 4d7e \texttt{id: 4d7e} id: 4d7e
小 H 在课堂上学习了异或运算。
对于两个非负整数 x , y x,y x,y,它们的异或是指,将它们作为二进制数,对二进制表示中的每一位进行如下运算得到的结果:
x x x 和 y y y 的异或被记为 x xor y x \operatorname{xor} y xxory 或 x ⊕ y x \oplus y x⊕y。
在 C++ 中,你可以用 x ^ y
得到
x
x
x 与
y
y
y 的异或值。
另外,若干个数的异或称之为异或和。
小 H 还了解到,一个长度为 n n n 的数列 a a a 的异或积是一个等长的数列 b b b,其中 b i b_i bi 等于数列 a a a 中除了 a i a_i ai 以外其他元素的异或和,即
b i = ⨁ j = 1 n [ j ≠ i ] a j b_i = \bigoplus \limits_{j = 1}^{n} [j\ne i] a_j bi=j=1⨁n[j=i]aj
例如,数列 { 1 , 2 , 3 , 4 } \{1, 2, 3, 4\} {1,2,3,4} 的异或积为 { 5 , 6 , 7 , 0 } \{5, 6, 7, 0\} {5,6,7,0}。
异或积变换是指将一个数列用它的异或积替换的过程,由于异或积变换之后数列长度不变,所以异或积变换可以连续进行多次。
现在,小 H 有一个长度为 n n n 的数列 a a a,他想请你帮他计算出 a a a 经过 k k k 次异或积变换之后得到的序列。
本题单个测试点内有多组测试数据。
第一行一个整数 T T T,表示测试数据组数。
对于每一组测试数据:
第一行两个整数 n , k n,k n,k。
第二行 n n n 个整数 a 1 , a 2 , ⋯ , a n a_1,a_2,\cdots,a_n a1,a2,⋯,an。
对于每一组测试数据:
一行 n n n 个整数,表示数列 a a a 经过 k k k 次异或积变换之后得到的数列。
1
4 1
1 2 3 4
5 6 7 0
1
4 2
0 0 0 1
0 0 0 1
见附件中的 samples/xor3.in
见附件中的 samples/xor3.ans
此样例即为题目描述中的例子。
第 1 1 1 次异或积变换: { 0 , 0 , 0 , 1 } → { 1 , 1 , 1 , 0 } \{0,0,0,1\}\to\{1,1,1,0\} {0,0,0,1}→{1,1,1,0};
第 2 2 2 次异或积变换: { 1 , 1 , 1 , 0 } → { 0 , 0 , 0 , 1 } \{1,1,1,0\}\to\{0,0,0,1\} {1,1,1,0}→{0,0,0,1}。
对于 100 % 100\% 100% 的测试数据, 1 ≤ T ≤ 10 1 \le T \le 10 1≤T≤10, 2 ≤ n ≤ 1 0 5 2 \le n \le 10^5 2≤n≤105, 1 ≤ k ≤ 1 0 18 1 \le k \le 10^{18} 1≤k≤1018, 0 ≤ a i < 2 32 0 \le a_i < 2^{32} 0≤ai<232。
测试点编号 | n ≤ n\leq n≤ | k ≤ k \leq k≤ | 特殊性质 |
---|---|---|---|
1 ∼ 3 1 \sim 3 1∼3 | 100 100 100 | 100 100 100 | |
4 ∼ 5 4 \sim 5 4∼5 | 1000 1000 1000 | 1000 1000 1000 | |
6 ∼ 7 6 \sim 7 6∼7 | 3 3 3 | 1 0 18 10^{18} 1018 | |
8 ∼ 10 8 \sim 10 8∼10 | 1 0 5 10^5 105 | 3 3 3 | |
11 ∼ 13 11 \sim 13 11∼13 | 1 0 5 10^5 105 | 1 0 18 10^{18} 1018 | a a a 中所有数的异或和为 0 0 0 |
14 ∼ 15 14 \sim 15 14∼15 | 1 0 5 10^5 105 | 1 0 18 10^{18} 1018 | n n n 为奇数 |
16 ∼ 17 16 \sim 17 16∼17 | 1 0 5 10^5 105 | 1 0 18 10^{18} 1018 | n n n 为偶数 |
18 ∼ 20 18 \sim 20 18∼20 | 1 0 5 10^5 105 | 1 0 18 10^{18} 1018 |
在 C++ 中,对于数据范围 0 ≤ x < 2 32 0\le x<2^{32} 0≤x<232,你可以:
unsigned int x
来定义;cin >> x
或 scanf("%u", &x)
来输入;cout << x
或 printf("%u", x)
来输出。可以发现当 n 为偶数时,答案将会在 原来的式子 与 变换一次的式子 之间徘徊。
同样,我们也可以验证 n 是奇数的性质:除第一次外其他都是 变换一次的式子。
只需要特判 n 和 k 均为偶数时即可。
#include <bits/stdc++.h> using namespace std; typedef long long ll; ll T, n; ll k; ll a[100007], b[100007]; inline void work() { ll sum = 0; for(int i = 1; i <= n; i++) sum ^= a[i]; for(int i = 1; i <= n; i++) b[i] = sum ^ a[i]; } int main() { ios::sync_with_stdio(0); cin.tie(0);cout.tie(0); cin>>T; while(T--) { cin>>n>>k; for(int i = 1; i <= n; i++)cin>>a[i]; if(n % 2 == 0 && k % 2 == 0) { for(int i = 1; i <= n; i++) cout<<a[i]<<" "; cout<<endl; continue; } work(); for(int i = 1; i <= n; i++) cout<<b[i]<<" "; cout<<endl; } return 0; }
给定一个长度为 n n n 的序列,一共有 q q q 次询问,每次询问给定正整数 x x x,然后依次执行以下操作:
注意,在每个询问过后序列是发生变化的。
几个需要说明的地方:
第一行两个正整数 n , q n,q n,q 表示序列长度和询问个数。
第二行 n n n 个正整数 a i a_i ai 表示一开始的序列。
接下来 q q q 行,每行一个正整数 x x x 表示一个询问。
输出 q q q 行,一行一个整数表示每个询问的答案。
5 2
1 2 3 4 5
1
1
4
5
10 10
5 9 8 3 5 7 10 19 5 24
10
56
19
14
18
53
52
57
96
1000
2
2
2
4
2
3
3
2
2
2
对于第一组样例,序列初始是 { 1 , 2 , 3 , 4 , 5 } \{1,2,3,4,5\} {1,2,3,4,5},第一次询问给定 x = 1 x=1 x=1,则异或后的序列为 { 0 , 3 , 2 , 5 , 4 } \{0,3,2,5,4\} {0,3,2,5,4}。区间 [ 2 , 5 ] [2,5] [2,5] 中的每个整数 2 , 3 , 4 , 5 2,3,4,5 2,3,4,5 都在这个序列中,这是满足条件的最大区间,所以答案为 5 − 2 + 1 = 4 5-2+1=4 5−2+1=4。
本题开启捆绑测试。
Subtask \text{Subtask} Subtask | 分值 | n , q ≤ n,q\leq n,q≤ | a i ≤ a_i\leq ai≤ | x ≤ x\leq x≤ |
---|---|---|---|---|
0 0 0 | 10 10 10 | 1 0 3 10^3 103 | 1 0 3 10^3 103 | 1 0 3 10^3 103 |
1 1 1 | 20 20 20 | 5 × 1 0 5 5\times10^5 5×105 | 1 0 3 10^3 103 | 1 0 3 10^3 103 |
2 2 2 | 10 10 10 | 5 × 1 0 5 5\times10^5 5×105 | 1 0 3 10^3 103 | 5 × 1 0 5 5\times10^5 5×105 |
3 3 3 | 60 60 60 | 5 × 1 0 5 5\times10^5 5×105 | 5 × 1 0 5 5\times10^5 5×105 | 5 × 1 0 5 5\times10^5 5×105 |
对于全部数据,保证: 1 ≤ n , q , a i , x ≤ 5 × 1 0 5 1\leq n,q,a_i,x\leq 5\times10^5 1≤n,q,ai,x≤5×105。
#include <bits/stdc++.h> using namespace std; const int M = 25, N = 1 << 19 | 5; int m = 19, n = 1 << m, q, t, x, y; struct node{ int l, r, m, p; }s[2][N]; inline void pushup(node &u, node &l, node &r) { u.l = l.p ? l.l + r.l : l.l; u.r = r.p ? r.r + l.r : r.r; u.m = max(l.r + r.l, max(l.m, r.m)); u.p = l.p & r.p; } int main() { ios::sync_with_stdio(0); cin.tie(0);cout.tie(0); cin>>q>>t; while(q -- )cin>>x, s[0][x] = {1, 1, 1, 1}; for(int i = 1, x = 1, y = 0; i <= m; i ++ , x ^= 1, y ^= 1) for(int j = 0; j < n; j ++ ) pushup(s[x][j], s[y][j], s[y][j ^ (1 << i - 1)]); while(t -- )cin>>x, y ^= x, cout<<s[1][y].m<<endl; return 0; }
这是我的第十三篇文章,如有纰漏也请各位大佬指正
辛苦创作不易,还望看官点赞收藏打赏,后续还会更新新的内容。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。