题目大意:
给你n个数,你可以交换一个数的任意二进制位,问你可以选出多少区间经过操作后异或和是0。
思路分析:
根据题目,很容易知道,对于每个数,我们可以无视它的1在那些位置,只要关注它有几个1即可,如果它的1的数量可以通过加减为0,那么这个区间就是合法的。
可以看到,两个数(分别有x,y个1,且x〉y)通过交换位置再进行异或操作,可能获得的新数中1的数量为y-x,y-x+2,y-x+4,...,x+y-2,x+y。
那么对于每一个左端点(l),它的合法右端点(r)的数量为sum[r]-sum[l-1]为偶数的r的个数(sum为前缀和数组)。同时,这个区间还要满足区间内1的数量最多的数(假设它在第k位)要小于sum[k]-sum[l-1]的一半,因为假如说它不满足这一条件的话,那么就没有足够多的1同它相抵消,也就不会合法。
那么我们应该如何避免这种状况呢?
很简单,可能会是不合法右端点的几个点(最多64个)直接暴力做就行了。
代码:
var a,sum,odd,even:array[0..300000]of longint; i,j,s,max,n,m:longint; ans,x:int64; begin read(n); for i:=1 to n do begin read(x); while x>0 do begin a[i]:=a[i]+x and 1; x:=x>>1; end; end; for i:=1 to n do begin sum[i]:=sum[i-1]+a[i]; odd[i]:=odd[i-1]; even[i]:=even[i-1]; if sum[i]and 1=1 then inc(odd[i]) else inc(even[i]); end; for i:=1 to n do if sum[i-1]and 1=1 then ans:=ans+odd[n]-odd[i-1] else ans:=ans+even[n]-even[i-1]; writeln(ans); for i:=1 to n do begin if i+64>n then m:=n else m:=i+64; s:=0; max:=0; for j:=i to m do begin if a[j]>max then max:=a[j]; s:=s+a[j]; if (2*max>s)and(s mod 2=0) then dec(ans); end; end; writeln(ans); end.