【位运算】浅谈异或

异或(XOR)运算性质

  1. 定义 :相同为0,不同为1
    0 ⊕ 0 = 0
    0 ⊕ 1 = 1
    1 ⊕ 0 = 1
    1 ⊕ 1 = 0
    
  2. 具有交换律和结合律
  3. 自反性 一个数和自己异或得0
    a ⊕ a = 0
    
  4. 与0异或不变
    a ⊕ 0 = a
    
  5. 与1异或取反
    a ⊕ 1 = ~a(按位取反)
    
  6. 可逆性
    a ⊕ b ⊕ b = a
    
  7. 奇偶性:在序列异或中非常常用
    多个值异或的结果取决于1的个数:
    • 某一位上1的总数为奇数 → 结果为1
    • 某一位上1的总数为偶数 → 结果为0
  8. 等式性质
    a ⊕ b = c  ⇔ a ⊕ c = b ⇔ b ⊕ c = a
    
  9. 分配律(与AND结合)
    a & (b ⊕ c) = (a & b) ⊕ (a & c)
    

异或运算实用技巧

  1. 变量交换(无临时变量)
    a ^= b;
    b ^= a;
    a ^= b;

原理

  • a ^= b → a = a ⊕ b
  • b ^= a → b = b ⊕ (a ⊕ b) = a
  • a ^= b → a = (a ⊕ b) ⊕ a = b
  1. 找缺失数字(数组 0 到 n 中缺一个数)
int findMissingNumber(vector<int>& nums, int n) {
    int result = 0;
    // 异或所有数字 0 到 n
    for (int i = 0; i <= n; i++) {
        result ^= i;
    }
    // 异或数组中的所有数字
    for (int num : nums) {
        result ^= num;
    }
    return result;
}

int main() {
    vector<int> nums = {0, 1, 3, 4, 5}; // 缺失 2
    int n = 5;
    cout << "缺失的数字是: " << findMissingNumber(nums, n) << endl;
}

输出

缺失的数字是: 2

所有数字异或两次会相互抵消,缺失数字只会异或一次

  1. 找奇数次出现元素(其他元素出现偶数次)
int findOddOccurrence(vector<int>& nums) {
    int result = 0;
    for (int num : nums) {
        result ^= num;
    }
    return result;
}

int main() {
    vector<int> nums = {4, 3, 3, 4, 2, 4, 4, 3, 3, 2, 5};
    // 5 出现 1 次(奇数次)
    cout << "奇数次出现的元素: " << findOddOccurrence(nums) << endl;
}

输出

奇数次出现的元素: 5
  1. 位翻转(使用掩码)
int main() {
    unsigned char value = 0b10101010; // 170
    unsigned char mask = 0b11110000;  // 240
    
    // 翻转 mask 中为 1 的位
    unsigned char flipped = value ^ mask;
    
    cout << "原始值: " << (int)value 
         << " (二进制: " << bitset<8>(value) << ")" << endl;
    cout << "掩码值: " << (int)mask 
         << " (二进制: " << bitset<8>(mask) << ")" << endl;
    cout << "翻转后: " << (int)flipped 
         << " (二进制: " << bitset<8>(flipped) << ")" << endl;
}

输出

原始值: 170 (二进制: 10101010)
掩码值: 240 (二进制: 11110000)
翻转后: 90 (二进制: 01011010)
  1. 奇偶校验位计算
bool computeParity(unsigned int data) {
    bool parity = false;
    while (data) {
        parity ^= (data & 1); // 异或最低位
        data >>= 1;           // 右移一位
    }
    return parity;
}

int main() {
    unsigned int numbers[] = {7, 10, 15, 255};
    
    for (unsigned int num : numbers) {
        cout << "数字 " << num << " 的奇偶校验位: " 
             << computeParity(num) << endl;
    }
}

输出

数字 7 的奇偶校验位: 1  (二进制 111 → 1的个数为3,奇数)
数字 10 的奇偶校验位: 0 (二进制 1010 → 1的个数为2,偶数)
数字 15 的奇偶校验位: 0 (二进制 1111 → 1的个数为4,偶数)
数字 255 的奇偶校验位: 0 (二进制 11111111 → 1的个数为8,偶数)
  1. 判断两个数是否异号
#include <iostream>
using namespace std;

bool oppositeSigns(int x, int y) {
    return (x ^ y) < 0;
}

int main() {
    cout << "10 和 -5 是否异号? " << boolalpha 
         << oppositeSigns(10, -5) << endl; // true
    cout << "10 和 5 是否异号? " << boolalpha 
         << oppositeSigns(10, 5) << endl;   // false
    cout << "-10 和 -5 是否异号? " << boolalpha 
         << oppositeSigns(-10, -5) << endl; // false
}

输出

10 和 -5 是否异号? true
10 和 5 是否异号? false
-10 和 -5 是否异号? false

原理
整数最高位是符号位(1 表示负,0 表示正),异号数的符号位不同,异或后符号位为 1(负数)

  1. 快速检查两个数是否相等
int main() {
    int a = 42, b = 42, c = 24;
    
    cout << "a 和 b 相等? " << (a ^ b ? "false" : "true") << endl;
    cout << "a 和 c 相等? " << (a ^ c ? "false" : "true") << endl;
    
    return 0;
}

输出

a 和 b 相等? true
a 和 c 相等? false

a == b 更快,因为不需要分支预测

  1. 加密/解密简单实现
#include <iostream>
#include <string>
using namespace std;

string xorEncryptDecrypt(const string& input, char key) {
    string result = input;
    for (char& c : result) {
        c ^= key;
    }
    return result;
}

int main() {
    string message = "Hello, XOR!";
    char key = 'K'; // 加密密钥
    
    string encrypted = xorEncryptDecrypt(message, key);
    cout << "加密后: " << encrypted << endl;
    
    string decrypted = xorEncryptDecrypt(encrypted, key);
    cout << "解密后: " << decrypted << endl;
    
    return 0;
}

输出

加密后: +IYYJ
X\SK
解密后: Hello, XOR!

原理
应用两次异或恢复原始数据:
data ⊕ key ⊕ key = data

  1. 位操作技巧集合
#include <iostream>
using namespace std;

int main() {
    // 1. 设置最低有效位为0
    int x = 7; // 0111
    x &= (x - 1);
    cout << "设置最低位为0: " << x << " (二进制: " << bitset<4>(x) << ")" << endl;
    
    // 2. 检查是否是2的幂
    int y = 16;
    bool isPowerOfTwo = (y & (y - 1)) == 0;
    cout << y << " 是2的幂? " << boolalpha << isPowerOfTwo << endl;
    
    // 3. 交换特定位
    unsigned char a = 0b11001100;
    unsigned char b = 0b00110011;
    unsigned char mask = 0b11110000;
    unsigned char swapped = (a & mask) | (b & ~mask);
    cout << "交换特定位: " << bitset<8>(swapped) << endl;
    
    return 0;
}

输出

设置最低位为0: 6 (二进制: 0110)
16 是2的幂? true
交换特定位: 11000011
  1. 找出只出现一次的数字(其他出现三次)
#include <iostream>
#include <vector>
using namespace std;

int singleNumber(vector<int>& nums) {
    int ones = 0, twos = 0;
    for (int num : nums) {
        ones = (ones ^ num) & ~twos;
        twos = (twos ^ num) & ~ones;
    }
    return ones;
}

int main() {
    vector<int> nums = {2, 2, 3, 2, 4, 4, 4}; // 3 出现一次
    cout << "只出现一次的数字: " << singleNumber(nums) << endl;
    return 0;
}

输出

只出现一次的数字: 3

例题


来看一道简单题

与或和的异或

题目描述

给定一个长为 n n n 的正整数序列 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,,an

给定两个数 l , r ( 1 ≤ l ≤ r ≤ n ) l,r(1≤l≤r≤n) l,r1lrn,要求计算
( a l & a l + 1 & ⋯ & a r ) ⊕ ( a l ∣ a l + 1 ∣ ⋯ ∣ a r ) (a_l \& a_{l+1} \& \cdots \&a_r) ⊕ (a_l|a_{l+1}| \cdots |a_r) (al&al+1&&ar)(alal+1ar)
其中 & \& & , ∣ | , ⊕ ⊕ 分别是按位与、按位或和按位异或运算。

输入格式

第一行输入两个数 n ( 1 ≤ n ≤ 1 0 5 ) n(1≤n≤10^{5} ) n(1n105) , m ( 1 ≤ m ≤ 1 0 6 ) m(1≤m≤10^{6}) m(1m106),表示序列长度和操作数。
第二行,n 个数 a 1 , a 2 , … a n a_1,a_2,…a_n a1,a2,an 0 ≤ a i ≤ 1 0 9 0≤a_i≤10^9 0ai109),表示正整数序列。
接下来 m 行,每行 2 个数 l , r ( 1 ≤ l ≤ r ≤ n ) l,r (1≤l≤r≤n) l,r(1lrn)

输出格式

m 行,每行一个数,表示对应操作的答案。

核心思路按位 + 前缀和

  • 根据与、或、异或的性质,可以轻松地观察发现,对于子序列(即 a l a_l al a r a_r ar)的二进制第 k 位,当且仅当它们有 0 有 1 时,才对最后的结果有贡献。具体如下:
    (1 & 1 & 1) ^ (1 | 1 | 1)  =  1 ^ 1 = 0
    (1 & 1 & 0) ^ (1 | 1 | 0)  =  0 ^ 1 = 1
    (0 & 0 & 0) ^ (0 | 0 | 0)  =  0 ^ 0 = 0
    
  • 为提高效率,需要采用前缀和的思想。对于整个序列 { a n } \{a_n\} {an} ,统计前 i 个数中,第 k 位的 1 的个数,存入pre[i][k].
  • 当查询 l l l r r r 的子序列时,可以通过 pre[r][k] - pre[l-1][k] 来获得子序列中所有数的二进制第 k 位的 1 的个数,将其与子序列长度 r - l + 1 进行比较,即可知道该位是否有0有1,若是则对总结果有贡献.
  • 贡献累加:第 k 位若对最终结果有贡献,则其贡献就为 2 k 2^k 2k
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int n,m,a[N],pre[N][32];

int read(){
	int x=0,f=1; char ch=getchar();
	if(ch=='-') f=-1, ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	return x*f;
}

// 计算前缀和pre
void op(int t){
	for(int i = 0; i <= 30; i ++){
		pre[t][i] = pre[t-1][i];
		if((a[t] >> i) & 1 == 1) pre[t][i] ++;
	}
}

// 查询
void solve(int l, int r){
	int t = r - l + 1;
	ll ans = 0;
	for(int i = 0; i <= 30; i ++){
		if(pre[r][i] - pre[l-1][i] < t && pre[r][i] - pre[l-1][i] > 0) 
			ans += (1ll << i);
	}
	printf("%lld\n", ans);
}

int main(){
	n = read(); m = read();
	for(int i = 1; i <= n; i ++){
		a[i] = read();
		op(i);
	}
	for(int i = 1; i <= m; i ++){
		int l, r;
		l = read(); r = read();
		solve(l, r);
	}
	return 0;
}

看一道异或序列相关的题

P3917异或序列

同时也是23年蓝桥省A的一道题

题目分析
题目要求计算序列 A 1 , A 2 , ⋯   , A N A_1, A_2, \cdots, A_N A1,A2,,AN 的所有连续子序列的异或和的总和。即:
∑ 1 ≤ i ≤ j ≤ N ( A i ⊕ A i + 1 ⊕ ⋯ ⊕ A j ) \sum_{1 \leq i \leq j \leq N} (A_i \oplus A_{i+1} \oplus \cdots \oplus A_j) 1ijN(AiAi+1Aj)
其中 ⊕ \oplus 表示按位异或操作。 ( n ≤ 1 0 5 , A i ≤ 1 0 9 ) (n \leq 10^5 , A_i \leq 10^9) (n105,Ai109)

核心思路:依旧是按位 + 前缀和
异或操作具有按位独立性,即整个子序列的异或和可以拆分为每个二进制位的独立贡献。因此,我们可以分别计算每个二进制位对答案的贡献,最后将所有位的贡献相加。

∑(所有子序列异或和)
= ∑(每位贡献)
= ∑ₖ[2ⁱ × (第i位为1的子数组数量)]
= ∑ₖ[2ⁱ × ∑ⱼ(val[i][!tⱼ])]
  • 前缀异或数组
    • 定义前缀异或数组 pre[],其中 p r e 0 = 0 pre_0 = 0 pre0=0 p r e i = A 1 ⊕ A 2 ⊕ ⋯ ⊕ A i pre_i = A_1 \oplus A_2 \oplus \cdots \oplus A_i prei=A1A2Ai ( 1 ≤ i ≤ N ) ( 1 \leq i \leq N ) (1iN)
    • 根据异或的性质,任意子序列 [ i , j ] [i, j] [i,j] 的异或和可以表示为 p r e j ⊕ p r e i − 1 pre_j \oplus pre_{i-1} prejprei1
  • 按位贡献计算
    • 对于二进制第 k k k 位(最低位为第 0 位),子序列 [ i , j ] [i, j] [i,j] 在该位的贡献为 1 当且仅当 B j B_j Bj B i − 1 B_{i-1} Bi1 在第 k k k 位的值不同。
    • 因此,问题转化为:对每个位 k k k,统计有多少个子数组在该位为1,即就是统计有多少对 ( l , r ) (l, r) (l,r) 使得 p r e l pre_l prel p r e r pre_r prer 在第 k k k 位不同。
  • 贡献累加
    • 维护一个二维数组 cnt[k][0/1],记录遍历过程中前缀异或值在第 k k k 位为 0 或 1 的个数。
    • 从左到右遍历前缀异或数组 p r e 0 , p r e 1 , ⋯   , p r e N pre_0, pre_1, \cdots, pre_N pre0,pre1,,preN
      • 对于当前值 p r e i pre_i prei,检查其第 k k k 位的值 t t t(0 或 1)。
      • 能与 p r e i pre_i prei形成有贡献子序列的是 所有第i位为 !t 的历史前缀值 ,即第 k k k 位与 p r e i pre_i prei 相反的所有的 p r e j pre_j prej ( j < i ) (j<i) (j<i),这些子序列(即 [ j + 1 , i ] [j+1,i] [j+1,i])的个数为 cnt[k][!t],那么它们对最终答案的贡献值就是 个数 × 2^k = cnt[k][!t] × (1ll<<k)
      • 还要记得将当前 p r e i pre_i prei 在第 k k k 位是 0 还是 1 的计数 加入 cnt[k][t]
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=1e5+5;
int n, pre[N], cnt[33][2];
ll ans;

void op(int x){
	for(int i = 0; i <= 30; i ++){
		int t = (x >> i) & 1; // 取x的二进制第i位 t为0或1
		ans += (1ll << i) * cnt[i][!t]; // 贡献累加过程
		cnt[i][t] ++;
	}
}

int main(){
	scanf("%d", &n);
	op(0);
	for(int i = 1; i <= n; i ++){
		int x;
		scanf("%d", &x);
		pre[i] = pre[i-1] ^ x;
		op(pre[i]);
	}
	printf("%lld", ans);
	
	return 0;
}

再看一道题

P9223 「PEOI Rd1」异或(xor)

题目分析
给定两个正整数 n , m n, m n,m,求:

∑ i = 1 n ∑ j = 1 m ( i ⊕ j ) \sum_{i=1}^{n} \sum_{j=1}^{m} (i \oplus j) i=1nj=1m(ij)
其中, ⊕ \oplus 表示按位异或运算)。答案对 998244353 取模。共T组询问。 1 ≤ n , m ≤ 1 0 16 1 \leq n,m \leq 10^{16} 1n,m1016 T ≤ 50 T \leq 50 T50 2 54 = 18014398509481984 ≈ 1.8 × 1 0 16 2^{54} = 18014398509481984 \approx 1.8 \times 10^{16} 254=180143985094819841.8×1016

我们需要计算所有 1 ≤ i ≤ n 1 \leq i \leq n 1in 1 ≤ j ≤ m 1 \leq j \leq m 1jm i ⊕ j i \oplus j ij 的总和。直接暴力枚举所有 i i i j j j 时间复杂度为 O ( n m ) O(nm) O(nm),显然不可行。

核心思路逐位处理,统计所有有贡献的 ( i , j ) (i,j) (i,j) 对数,并乘以该位的权值 2 k 2^k 2k

  • 按位分解:对每一位 k k k(从 0 到最大可能的位数 53),统计满足以下条件的 ( i , j ) (i,j) (i,j) 数量:
    • i i i 的第 k k k 位为 0 ( n . n u m 0 ) (n.num0) (n.num0) j j j 的第 k k k 位为 1 ( m . n u m 1 ) (m.num1) (m.num1)
    • i i i 的第 k k k 位为 1 ( n . n u m 1 ) (n.num1) (n.num1) j j j 的第 k k k 位为 0 ( m . n u m 0 ) (m.num0) (m.num0)
    • 根据乘法原理,总数为 n . n u m 0 × m . n u m 1 + n . n u m 1 × m . n u m 0 n.num0 \times m.num1 + n.num1 \times m.num0 n.num0×m.num1+n.num1×m.num0
  • 贡献计算:将上述总数乘以 2 k 2^k 2k,累加到最终答案中。
  • 如何高效统计某一位上的 0 和 1 的数量?
    对于任意整数 x x x 和位 k k k,我们可以利用二进制周期性快速统计 1~x 中该位为 0 或 1 的数量。观察:
    0 0 0 0
    0 0 0 1
    0 0 1 0 
    0 0 1 1
    0 1 0 0
    0 1 0 1
    0 1 1 0
    0 1 1 1 
    1 0 0 0
    1 0 0 1
    1 0 1 0
    1 0 1 1 
    1 1 0 0 
    1 1 0 1
    1 1 1 0
    1 1 1 1
    
    • 对于 0 ~ x - 1 这 x 个数,(从最低位第0位开始记),第 k 位的01周期长度为 2 k + 1 2^{k+1} 2k+1. 例如第 0 位的周期为 2 ,第 1 位的周期为4
    • 则记半周期t,完整周期的个数则为 num = x / (2 * t)
    • 仅考虑完整周期,这 x 个数中,0 和 1 的个数均为 num * t
    • 对于剩余的一个不完整周期,需要先得到其长度 res = x % (2 * t),将 res 与半周期 t 进行比较。若剩余的数不到半周期,则全是 0,就将 res 加到 0 的个数中,num0 = num * t + res ;若剩余的数超过半周期,则半周期为0,剩下的为1,num0 = num * t + tnum1 = num * t + res - t
    • 注意我们找的范围为0 ~ x - 1,所以在最初要将传入的数 x 自增 1,最后要减去 0 ,也就是使 num0 减一,即可转换范围为 1 ~ x.

时间复杂度分析

  • 每组数据处理 O ( 54 ) O(54) O(54) 位,每组时间复杂度为 O ( 1 ) O(1) O(1)
  • 总体时间复杂度为 O ( T × 54 ) O(T \times 54) O(T×54),可轻松应对 T = 1 0 5 T = 10^5 T=105 的规模。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

int T;
const ll MOD=998244353;
struct node{
	ll num0, num1;
};

// 把所有的模加和模乘换成这两个函数 本题需要极致的mod 不然过不了
ll add(ll a, ll b){
	return ((a % MOD+b % MOD) % MOD + MOD) % MOD;
}
ll multi(ll a, ll b){
	return (a % MOD) * (b % MOD) % MOD;
}

// 获取1~x在第k位分别为 0 或 1 的个数 
node get_num(ll x, int i){
	node e;
	x ++; // 0~x-1 -> 1~x
	ll t = (1ll << i); // 一个0,1周期的长度的一半 也就是一周期内0或1的个数 
	ll num = x / (2 * t); // 周期个数 
	ll res = x % (2 * t); // 剩余的不到一个周期的数的个数 
	if(res <= t){ // 剩余的数不到半周期 则全是0 
		e.num0 = num * t + res;
		e.num1 = num * t;
	}else{ // 剩余超过半周期 则半周期为0 剩下的为1 
		e.num0 = num * t + t;
		e.num1 = num * t + res - t;
	}
	e.num0 --;
	return e;
}

void cal(ll x, ll y){
	ll ans = 0;
	for(int i = 0; i <= 53; i ++){
		node n = get_num(x, i);
		node m = get_num(y, i);
		ans = add(ans, add(multi(multi((1ll<<i), n.num0), m.num1),  multi(multi((1ll<<i), n.num1), m.num0)));
		// 原公式为 ans+=(1ll<<i)*(n.num0*m.num1+n.num1*m.num0); 
	}
	printf("%lld\n", ans);
}

int main(){
	scanf("%d", &T);
	while(T --){
		ll n, m;
		scanf("%lld%lld", &n, &m);
		cal(n, m);
	}
	return 0;
}

看一道构造最大值的异或题

MAX XOR

构造一个非负整数数列 { a 1 , a 2 , ⋯   , a n } \{a_1, a_2, \cdots, a_n\} {a1,a2,,an} 满足 0 ≤ a i ≤ x i 0 \leq a_i \leq x_i 0aixi 使得 S = a 1 ⊕ a 2 ⋯ ⊕ a n S = a_1 \oplus a_2 \cdots \oplus a_n S=a1a2an 最大。共 T 组测试数据,每组数据给出数列 { x n } \{x_n\} {xn},求出每组最大的 S S S并输出。保证所有测试数据的 n n n 的总和不超过 1 0 5 10^5 105

核心思路:按位+贪心

  • 贪心策略
    高位到低位逐位确定最大异或值,优先保证高位为1(因为高位值更大)
  • 位处理逻辑
    • st:存储当前仍受限制的数字(上界值), fr:自由数计数器(这些数字可任意设置各个位的数)
    • 存在自由数时 (fr >= 1)
      • 直接设置当前位为1(自由数可保证该位为1)
      • 跳过当前位后续处理
    • 无自由数时 (fr == 0)
      • 将数字分为两类:
        • c:当前位可自由选择0/1(上界 ≥ 2 bit 2^{\text{bit}} 2bit
        • cn:当前位只能选0(上界 < 2 bit 2^{\text{bit}} 2bit
      • 若有可选项(!c.empty()):
        • 设置当前位为1(由1^0^0^0^0...=1得来,所以接下来我们只需要在可选项中找一个数使其该位为1,可选项的剩余数该位为0)
        • 选择上界最大的数字设置当前位为1(保留最大灵活性),并将其低位放入受限数集合(因为其低位仍受限)
        • 其余可选项的当前位设为0,那么它们的低位就不再受限,变为自由数(fr += c.size() - 1),这样可以使低位的受限尽可能少,自由数尽可能多(贪心)
        • 更新状态列表为受限数集合(st = cn

时间复杂度

外层循环 31位(bit 30~0),内层循环每组数据 O ( n ) O(n) O(n),总复杂度为 O ( 31 × ∑ n ) O(31 \times \sum n) O(31×n),满足约束( ∑ n ≤ 1 0 5 \sum n \leq 10^5 n105 3.1 × 1 0 6 3.1 \times 10^6 3.1×106 操作)

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;

int t, n, ans, fr; // fr = 自由数计数器

void solve(){
    vector<int> st;  // 存储当前受限数字的上界
    ans = 0;         // 最大异或结果
    fr = 0;          // 自由数计数器清零
    
    // 读取输入数据
    for(int i = 1; i <= n; i++){
        int x;
        scanf("%d", &x);
        st.push_back(x);
    }
    
    // 从高位到低位逐位处理 (30位到0位)
    for(int bit = 30; bit >= 0; bit--){
        // 情况1:存在自由数时直接设置当前位为1
        if(fr >= 1){
            ans |= (1 << bit);  // 自由数可以保证当前位为1
            continue;           // 跳过当前位的后续处理
        }
        
        vector<int> c, cn;  // c: 可选项, cn: 不可选项
        ll val = (1LL << bit);  // 当前位对应的值
        
        // 分类处理:根据数字上界与当前位值的关系
        for(int i = 0; i < st.size(); i++){
            if(st[i] >= val) 
                c.push_back(st[i]);   // 可自由选择0或1
            else 
                cn.push_back(st[i]);  // 只能选择0
        } 
        
        // 情况2:没有自由数但有可选项时
        if(c.size() >= 1){
            ans |= (1 << bit);  // 设置当前位为1
            // 选择上界最大的数字设置当前位为1
            int tmp = *max_element(c.begin(), c.end());
            cn.push_back(tmp - (1 << bit));  // 将该数字的低位放入受限集合
            // 其余可选项变为自由数
            fr += c.size() - 1;
        }   
        // 更新状态为不可选项(继续处理低位)
        st = cn;
    }
    printf("%d\n", ans);
}

int main(){
    scanf("%d", &t);  // 测试数据组数
    for(int i = 1; i <= t; i++){
        scanf("%d", &n);  // 每组数据的数字个数
        solve();
    }
}

目前涉及到的都是一些异或的基础题。后面可能会继续更新。

### 异或运算的基本性质 异或运算具有以下几个基本性质: 1. **交换律** 对于任意两个整数 \(A\) 和 \(B\),有 \(A \oplus B = B \oplus A\)。这意味着在执行异或操作时,操作数的顺序不影响最终结果[^1]。 2. **结合律** 如果存在三个整数 \(A\)、\(B\) 和 \(C\),那么可以得到 \((A \oplus B) \oplus C = A \oplus (B \oplus C)\),这表明多个数值连续进行异或计算时,括号的位置不会影响结果。 3. **恒等律** 任何数与零做异或运算都会返回该数本身,即 \(A \oplus 0 = A\)。这是因为零的所有位均为 `0`,而按照异或定义,当其中一个操作数为 `0` 时,结果等于另一个操作数。 4. **自反性/逆元特性** 当一个数与其自身相异或时,结果总是 `0`,表达式形式为 \(A \oplus A = 0\)。这是由于每一位上相同的值会相互抵消。 5. **幂等性缺失** 虽然某些其他逻辑运算可能具备幂等性(比如 AND 或 OR),但是 XOR 不满足这一属性——重复施加同一个输入并不会保持原样不变而是变为相反状态或者归零。 ### 异或运算的应用场景 基于上述提到的独特性质,异或广泛应用于多种领域和技术实现当中: - **数据加密解密** 利用异或不可预测性和可逆特点,在密码学里经常作为基础算法之一来构建简单的流加密方案。例如通过固定密钥对明文逐比特实施一次一密技术即可完成初步保护措施[^2]。 - **错误检测机制** 在通信协议设计过程中采用奇偶校验方法时也会涉及到异或概念;发送端先统计有效载荷里面含有多少个‘1’再附加额外标志位使得整体数量成为期望模式(单数还是双数取决于具体需求), 接收方收到消息后再重新验证一遍从而判断传输期间是否存在误码现象发生. - **快速查找丢失重复项等问题解决策略** 借助于自我消除特性的优势, 可以巧妙地处理数组中寻找唯一元素之类的挑战型题目. ```python def find_unique_element(nums): result = 0 for num in nums: result ^= num return result ``` 此函数利用了异或的自反性原理,遍历列表中的每一个数字并累积它们之间的异或值,最后剩下的那个未被配对的就是我们要找的目标独特值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值