D-子段异或(异或前缀和)
1.题意:
2.题解:
详见参考博客
设a[i]为输入的数
b[i]为异或前缀和
b[i] = a[1] ^ a[2] ^ … ^ a[i - 1] ^ a[i]
已知一个数x^y = 0当且仅当x == y时成立
那么对于一段[1,r]来说,异或前缀和为b[r],那么如果想要以r为异或为0的子段的右半部分,那么只需要前面出现过一个数b[i] == b[r],那么[i + 1,r]这一段异或和为0
就比如
对于x1,x2,x3…xr,如果想要找到一段区间[i,r]使得其区间异或和为0,设x1,x2,x3…xj的异或和为b[j]那么如果b[j] == b[r],也就是说b[j] ^ b[j+1,r] = b[r]而只有x ^ y == x当且仅当y = 0时成立。
相当于一条直线上的点,每次加的cnt[i]都代表把i点前符合要求的点和第i个点连起来构成的线段个数
特判cnt【0】一般都等于1
3.ac代码:
应该能ac,进不去
#include <bits/stdc++.h>
#include <math.h>
using namespace std;
typedef long long ll;
const int N = 50000 + 10;
int a[N],x[N];
map<ll,int> mp;//因为数组下标无法达到2^30,所以用map
int main(){
int n;
cin>>n;
mp[0]=1; //x[i]=0的特判
for(int i=1;i<=n;i++){
cin>>a[i];
x[i]=x[i-1]^a[i];
}
int cnt=0;
for(int i=1;i<=n;i++){
cnt+=mp[x[i]];
mp[x[i]]++;
}
cout<<cnt<<endl;
}
蓝桥杯姐妹题 前缀和-蓝桥杯省赛-K倍区间
#include <bits/stdc++.h>
#include <math.h>
using namespace std;
typedef long long ll;
const int N = 50000 + 10;
ll a[N],sum[N];
ll cnt[N];
int main(){
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
sum[i]+=a[i];
}
ll ans=0;
cnt[0]=1;//区间和为0的sum[0]已经有一个
for(int i=1;i<=n;i++){
ans+=cnt[sum[i]%k];
cnt[sum[i]%k]++;
}
cout<<ans<<endl;
}
CF242E XOR on Segment
1.题意:
1.对【l,r】区间每个值异或x
2.对区间求和
2.题解:
有用分块做的大佬,回头我再研究
将每个数字变成0/1二进制串,
每个线段树维护区间内第i位为1的个数
a XOR x 就是将a和x变成2进制,将a和x对应的每一位异或,变成了0/1异或,可以发现,
0/1 XOR 0 =0/1, 1/0 XOR 1=0/1 异或0不变,异或1相反
由于每个线段树维护的cnt[20],cnt【i】就是区间所有数字二进制时在第i位是1的个数,所以需要变的时候,只需要将这一位全取反,原来的为1的个数等于该区间0的个数,即区间长度-原来的1的个数
用懒标记,记录需要取反的次数,如果该位需要取反则当前区间该位变成相反的个数,同时懒标记个数++(父节点已经变化)
pushdown:将父标记下放到子标记上,同时父标记变为0,如果父标记是奇数,则改变原来子节点标记的奇偶性,子节点变化
3.ac代码:
#include <bits/stdc++.h>
#include <math.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int a[N];
const int INF=0x3f3f3f3f;
struct segment{
int l,r;
int cnt[22];//l~r区间中子段第i位上的1的个数
int tag[22];
}tree[N<<2];
void pushup(int u){
for(int i=0;i<20;i++)
tree[u].cnt[i]=tree[u<<1].cnt[i]+tree[u<<1|1].cnt[i];
}
void pushdown(int u){
for(int i=0;i<20;i++){
int p=tree[u].tag[i];
if(p){
tree[u<<1].tag[i]+=p;
tree[u<<1|1].tag[i]+=p;
if(p&1){//如果p是奇数,会改变原来子树tag的奇偶性,所以要变
tree[u<<1].cnt[i]=(tree[u<<1].r-tree[u<<1].l+1)-tree[u<<1].cnt[i];
tree[u<<1|1].cnt[i]=(tree[u<<1|1].r-tree[u<<1|1].l+1)-tree[u<<1|1].cnt[i];
}
tree[u].tag[i]=0;
}
}
}
void build(int u,int l,int r){
if(l==r){
tree[u]={l,r};
for(int i=0;i<20;i++){
if(a[l]>>i&1) tree[u].cnt[i]=1;
}
return ;
}
tree[u]={l,r};
int mid=tree[u].l+tree[u].r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
pushup(u);
}
void update(int u,int l,int r,int x){
if(tree[u].l>=l&&tree[u].r<=r){
for(int i=0;i<20;i++){
if(x>>i&1){
tree[u].cnt[i]=(tree[u].r-tree[u].l+1)-tree[u].cnt[i];
tree[u].tag[i]++;
}
}
return ;
}
int mid=tree[u].l+tree[u].r>>1;
pushdown(u);
if(l<=mid) update(u<<1,l,r,x);
if(r>mid) update(u<<1|1,l,r,x);
pushup(u);
}
ll query(int u,int l,int r){
if(tree[u].l>=l&&tree[u].r<=r){
ll ans=0,p=1;
for(int i=0;i<20;i++){
ans+=p*tree[u].cnt[i];
p<<=1;
}
return ans;
}
ll sum=0;
int mid=tree[u].l+tree[u].r>>1;
pushdown(u);
if(l<=mid) sum+=query(u<<1,l,r);
if(r>mid) sum+=query(u<<1|1,l,r);
return sum;
}
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
build(1,1,n);
int m;
int p,l,r,x;
cin>>m;
for(int i=1;i<=m;i++){
cin>>p>>l>>r;
if(p==1){
cout<<query(1,l,r)<<endl;
}else{
cin>>x;
update(1,l,r,x);
}
}
}
异或和(位运算)
1.题意:
给一个n个元素的数组,求n个元素两两异或的和
2.题解:
参考
将所有元素都变为二进制串,显然和为某二进制位为1的个数二进制权值,在该位上为1的应该由该位上为0和为1异或得来的,所以该位上为0的数字的个数该位上为1的数字的个数,为所有异或后该位为1的情况,
3.ac代码:
#include <bits/stdc++.h>
#include <math.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
int a[N];
int cnt[22];
int n;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
for(int j=0;j<=20;j++){
if(a[i]>>j&1) cnt[j]++;
}
}
ll sum=0;
for(int i=0;i<=20;i++){
sum=sum+(ll)cnt[i]*(ll)(n-cnt[i])*(ll)(1<<i);
}
cout<<sum<<endl;
}