位运算
1.要点
与:&
或:|
异或:^
非:~
异或运算性质:
(1)x^x=0
(2)x^0=x
(3)a^b^b=a
(1,2推出)
(4)a^b=c
->a=b^c
(两侧同异或b)
位运算按补码计算
- 正数的补码就是正数本身;
- 负数的补码 = 负数的绝对值正数补码取反 + 1
正数右移要用unsigned int最后才会变0(int高位补1)
(1)将一个数乘(除) 2 的非负整数次幂
x<<i
(乘以2的i次方)x>>i
(除以2的i次方)
(2)判断数字奇偶性:
x&1
(x为奇数,二进制最后一位为1,x&1=1,反之x为偶数,二进制最后一位为0,x&1=0.优化x%2)
(3)判断数字某一位1还是0:
(x>>i) & 1
(3)修改二进制中的某一位
1.改为1
x | (1<<i)
(将x从右数第i位改为1)
x:10010
1<<3:01000
结果:11010
2.改为0(将x从右数第i位改为0)
x & ~(1<<i):11010
1<<3:01000
~(1<<3):10111
结果:10010
(4)快速判断一个数字是否为2的幂次方
x & (x-1)
(若为0,x是2的幂次方,反之不是)
x:00100
x-1:00011
x & (x-1):00000
x为2的幂次方,x-1肯定借位,最终0和1错位,结果为0
x:00110
x-1:00101
x & (x-1):00100!=0
x不是2的幂次方,x-1不会借最高的1位,最终里面肯定有个1,结果不为0
(5)获取二进制位中最低位的1
lowbit(x)=x & -x
x:010010(18)
-x:101101+1=101110(负数的绝对值取反 + 1)
lowbit(x):000010
2.题目
2023异或和之和
重点:
(1)构造异或前缀和,利用上面异或的性质4,最终不是prefix[r]-prefix[l-1]
,而是prefix[r]^prefix[l-1]
,可以得90分
(2)优化:异或为位运算,考虑按位运算优化,考虑每一位0或1对当前位的贡献度(只有0^1=1).题目条件0-20位,对于每一位j,对于prefix[i],若prefix[i]=0,则算前面有多少个1,加上这个个数,就是这个0对这一位的贡献度,最终的和就表示有多少组0和1配对,就是当前位所有异或产生1的情况,然后次数乘上当前位的权重(cnt*(1<<j))
代码:
#include <bits/stdc++.h>using namespace std;typedef long long ll;
const int N=1e5+10;
ll a[N],n,prefix[N];
ll res;int main(){ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);cin>>n;for(int i=1;i<=n;i++) cin>>a[i];for(int i=1;i<=n;i++){prefix[i]=prefix[i-1]^a[i];}/*for(int l=1;l<=n;l++){for(int r=l;r<=n;r++){res+=prefix[r]^prefix[l-1];}}*/for(int j=0;j<=20;j++){ //A_i<=2^20int cnt0=0,cnt1=0; //0出现个数,1出现个数 ll cnt=0;for(int i=0;i<=n;i++){if( (prefix[i]>>j) & 1 ){ //当前位为1,cnt加上前面0的个数,1出现个数++ cnt+=cnt0;cnt1++;} else{cnt+=cnt1;cnt0++;} //当前位为0,cnt加上前面1的个数,0出现个数++}res+=(ll)cnt*(1<<j); //当前位的贡献度*当前位权重 }cout<<res;return 0;
}