1. 程式人生 > 實用技巧 >經典題 所有子區間和的異或和

經典題 所有子區間和的異或和

兩個經典的題目
1.求所有子區間異或和的和
2.求所有子區間和的異或和
第一個我們討論過 也做出來了
主要是第二個
首先肯定要進行字首和 記得把sum[0]算上
記sum為字首和陣列
顯然 一個區間的和 為sum[i]-sum[j-1]
另外異或肯定要拆位來看
考慮第k位 設 lim[i]為sum[i]%(2^k)
顯然
當sum[i]的第k位為1時,並且sum[j-1]的第k位為0時 需要滿足 lim[i]>=lim[j-1]
當sum[i]的第k位為1時,並且sum[j-1]的第k位為1時 需要滿足 lim[i]<lim[j-1] (這樣才會向高位借位 在k位產生1)
第k位為0時同理
顯然這是一個偏序關係 用樹狀陣列維護即可

#include<bits/stdc++.h>
using namespace std;
const int N = 2*(1e6+100);
typedef long long ll;
ll c[2][N],a[100005],p[30];
void add(int x,int v,int g){
	if(x==0){
		c[g][x]+=v;
		return;
	}
	while(x<N){
		c[g][x]+=v;
		x+=x&-x;
	}
}
ll query(int x,int g){
	int ret=c[g][0];
	while(x){
		ret+=c[g][x];
		x-=x&-x;
	}
	return ret;
}
int main(){
	p[0]=1;
	for(int i = 1; i <= 25; i++) p[i]=2*p[i-1];
	int n;
	scanf("%d",&n);
	for(int i = 1; i <= n; i++)
	scanf("%d",&a[i]),a[i]+=a[i-1];
	a[0]=0;
	ll ans = 0;
	for(int k = 0; (1<<k) <= a[n]; k++){
		ll ret = 0;
		for(int i = 0; i <= n; i++){
			int g = ((a[i]>>k)&1);
			ll lb = a[i]%p[k];
			if(g){
				ret+=query(lb,0)+query(N-1,1)-query(lb,1);
				add(lb,1,1); 
			}else{
				ret+=query(lb,1)+query(N-1,0)-query(lb,0);
				add(lb,1,0);
			}
		}
		if(ret%2) ans+=(1<<k);
		memset(c,0,sizeof(c));
	}
	printf("%lld\n",ans);
	return 0;
}