【模拟题】异或 (二进制)
异或
问题描述
给定序列\(𝐴\),计算\(\sum^{n}_{i=1}\sum^{n}_{j-i}A_i and A_{i+1} and .... and A_j\)
输入格式】
第一行一个整数\(𝑁\).
第二行\(𝑁\)个整数描述\(𝐴\).
输出格式
一行输出答案\(ans\).
样例输入
3
1 2 3
样例输出
8
数据规模
对于\(10\%\)的数据,\(𝑁 ≤ 100, 𝐴_i ≤ 1\).
对于\(20\%\)的数据,\(𝑁 ≤ 100\).
对于\(30\%\)的数据,\(𝑁 ≤ 1000\)
对于\(40\%\)的数据,\(𝑁 ≤ 100000\)
以上的数据档互不相交.
对于所有的数据,满足\(1 ≤ 𝑁 ≤ 10^5, 0 ≤ 𝐴_i ≤ 2^{31 − 1}\).
题解
我们可以去利用二进制的\(\&\)的性质,去计算二进制每一位的\(1\)的贡献。
因为\(\&\)后有一位变成\(0\),那么它永远不会有贡献了(永远不会变成\(1\)),然后去计算会有多少个数会有这个\(1\)(权值为\(1<<i\))的贡献。
注意,一定要用\(1LL\),否则\(2^{32}\)会炸。
code(&):
#include<iostream>
#include<cstdio>
#include<cctype>
#define N 100005
#define ll long long
#define R register
using namespace std;
template<typename T>inline void read(T &a){
char c=getchar();T x=0,f=1;
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
a=f*x;
}
int n,a[N],cnt;
ll ans;
int main(){
read(n);
for(R int i=1;i<=n;i++)read(a[i]);
for(R ll i=0;i<=32;i++){
cnt=0;
for(R int j=1;j<=n;j++){
if(a[j]&(1LL<<i))cnt++;
else cnt=0;
ans+=cnt*(1LL<<i);
}
}
printf("%lld\n",ans);
return 0;
}
拓展
后来大佬GMPotlc又告诉了我一个处理异或(^)的做法。
都是利用二进制中的性质,异或偶数次无贡献,异或奇数次才有贡献。
而且若现在\(1\)的个数为奇数,那么就会与前面偶数次的产生贡献;当前为偶数,则反之。
所以我们记录一个出现\(1\)奇数次的个数\(a\)和偶数次的个数\(b\),总个数\(cnt\)。
\(cnt\)为奇数 \(ans+=(b*(1LL<<i)),a++;\)
\(cnt\)为偶数 \(ans+=(a*(1LL<<i)),b++;\)
注意:初始化 ,\(b=1\)(因为一开始\(0\)个\(1\)也是偶数)。
code(^):
#include<iostream>
#include<cstdio>
#include<cctype>
#define N 100005
#define ll long long
#define R register
using namespace std;
template<typename T>inline void read(T &a){
char c=getchar();T x=0,f=1;
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
a=f*x;
}
int n,init[N],cnt,a,b;
ll ans;
int main(){
read(n);
for(R int i=1;i<=n;i++)read(init[i]);
for(R ll i=0;i<=32;i++){
cnt=0;a=0;b=1;
for(R int j=1;j<=n;j++){
if(init[j]&(1LL<<i))cnt++;
if(cnt&1)ans+=(1LL<<i)*b,a++;
else ans+=(1LL<<i)*a,b++;
}
}
printf("%lld\n",ans);
return 0;
}