[TJOI2017]异或和-题解

题目地址-luogu

题意见原题面。

这个题暴力的方法就是求出所有的连续和然后异或起来,通过预处理前缀和,可以在 O ( n 2 ) O(n^2) O(n2)的时间内求出。

一种优化是用 F F T FFT FFT直接暴力降复杂度,但是很不好写还很慢。

我们可以看,它只要求求最后的异或和,并没有要求求所有连续和,所以我们通过二进制位来考虑,一位异或出来是1当且仅当所有连续和里面有奇数个这一位为1,0不会影响所以不管0。

通过前缀和的思想,我们知道任意一段连续和都可以表示为两个前缀和相减,所以我们一位一位考虑,当什么情况两个相减时该位为1什么时候该位为0。

举个例子可以发现,如果当前考虑位为 1 1 1,那么分两种情况:

  1. 前面要减去的那个数的那一位也为1,那么当前这个1减去1后还要为1,只有前面不够减,向高位借位的时候才可能为1,所以要要减去的那个数后面的位大于当前这个数后面的位。如 ( 110 ) 2 − ( 101 ) 2 = ( 001 ) 2 , ( 1101 ) 2 − ( 0110 ) 2 = ( 0111 ) 2 (110)_2-(101)_2=(001)_2,(1101)_2-(0110)_2=(0111)_2 (110)2(101)2=(001)2,(1101)2(0110)2=(0111)2(当前考虑从右往左第3位)
  2. 前面要减去的那个数的那一位为0,那么只要不借位,当前这位就直接是1-0=1了,所以要求要减去的那个数后面的位小于等于当前这个数后面的位。如 ( 101 ) 2 − ( 001 ) 2 = ( 100 ) 2 , ( 101 ) 2 − ( 011 ) 2 = ( 010 ) 2 (101)_2-(001)_2=(100)_2,(101)_2-(011)_2=(010)_2 (101)2(001)2=(100)2,(101)2(011)2=(010)2(当前考虑从右往左第3位)

如果当前这个数这位是0的话,同样分两种情况考虑即可,是差不多的。

  1. 要减去的那个数当前这个位为1,那么由于肯定要借位,只需后面的小于等于当前这个数的后面的位即可。
  2. 为0的话,为了要借位,只需大于即可。

所以我们用个权值树状数组,每次就可以快速统计有多少个1了。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define lowbit(a) ((a)&-(a))
using namespace std;
const int M=1e5+10,N=1e6+10;
int n;ll A[M],maxv;
ll now[M];
struct Bit_Tree{
	int B[N];
	void clear(){memset(B,0,sizeof(B));}
	void add(int a){for(;a<=maxv;a+=lowbit(a))++B[a];}
	int query(int a){int res=0;for(;a;a-=lowbit(a))res+=B[a];return res;}
	int back(int a){return query(maxv)-query(a);}
}B1,B0;
ll ans;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){scanf("%lld",&A[i]);A[i]+=A[i-1];if(A[i]>maxv)maxv=A[i];}
	for(int lg=0,rec;(1ll<<lg)<maxv;++lg){
		B1.clear();B0.clear();
		rec=0;B0.add(1);
		for(int i=1,id,q;i<=n;i++){
			id=A[i]&(1ll<<lg);
			if(id) q=B0.query(now[i]+1)+B1.back(now[i]+1);
			else q=B1.query(now[i]+1)+B0.back(now[i]+1);
			if(q&1)rec^=1;id>0?B1.add(now[i]+1):B0.add(now[i]+1);
			now[i]|=id;
		}
		if(rec)ans|=(1ll<<lg);
	}	printf("%lld\n",ans);
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

VictoryCzt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值