题意:长度为N的序列,求
首先对于这种异或求和的问题,一般都是按位拆分然后分别计算贡献。
所以上式子可以化简为
(b【k】指L~R所有元素二进制分解后第K位的异或和)。
两个N的for循环显然会超时,所以想办法优化,我们不妨先将K的枚举提到最前面。
先不考虑异或和的问题,式子的后半部分显然是求一个区间的最大值,首先想到单调队列,O(N)遍历序列得到
当前子串的最大值,但是单调队列并不适合求整个序列中具体到每一个字串的最大值。,,,,总之,这道题应该
用单调栈去求第i个数作为最大值所对应的最大区间。然后计算A中每个元素所产生的贡献。
然后,我们的表达式可以变成:
(tmp表示a【i】对应的区间数目,xor表示对应区间的异或和)。
显然tmp相关的求和式要么等于0,要么等于1,不用管这个,总之现在的问题变为给一个区间(L,R),
区间元素为A数组对应二进制位的第K位,A中最大值的对应坐标为index,问有多少个包含index的异或
和等于1的子串。
这里就需要用到异或的性质,首先我们知道:S2^..S6^S7==S7^S1。(S是递推过去的异或和)。
所以回到本题,我们枚举K求出相应区间的所有第K位二进制位构成B数组,然后对B数组做异或和得到sum数组。
现在考虑tmp相关的求和式O(1)计算式。我们从index位置开始考虑,如果B【index】为1,那么想得到一个经过
index的最钟异或和为1的子串,子串的左边界-1的sum值只能为0,如果B【index】为零,左边界-1为1。所以只要
计算index左边右边0/1的个数然后对应相乘就好了。
AC代码:
using namespace std;
#define MAXN 100010
#define MAXM 1010
#define INF 1000000000
#define MOD 1000000061
#define ll long long
#define eps 1e-8
int n;
int a[MAXN];
int b[MAXN];
int v[MAXN];
int s[MAXN];
int l[MAXN],r[MAXN];
int st[MAXN],tp;
ll ans;
ll cal(){
int i;
ll re=0;
for(i=1;i<=n;i++){
s[i]=s[i-1]+v[i];
}
for(i=1;i<=n;i++){
ll s1=i-l[i]+1;
ll s2=r[i]-i+1;
ll t1=s[i-1]-s[max(0,l[i]-2)];
ll t2=s[r[i]]-s[i-1];
(re+=t1*(s2-t2)%MOD*a[i])%=MOD;
(re+=(s1-t1)*t2%MOD*a[i])%=MOD;
}
return re;
}
int main(){
int i,j;
int tmp;
scanf("%d",&tmp);
while(tmp--){
ans=0;
scanf("%d",&n);
a[0]=a[n+1]=INF*2;
for(i=1;i<=n;i++){
scanf("%d",&a[i]);
b[i]=b[i-1]^a[i];
}
st[tp=1]=0;
for(i=1;i<=n;i++){
while(a[st[tp]]<=a[i]){
tp--;
}
l[i]=st[tp]+1;
st[++tp]=i;
}
st[tp=1]=n+1;
for(i=n;i;i--){
while(a[st[tp]]<a[i]){
tp--;
}
r[i]=st[tp]-1;
st[++tp]=i;
}
for(i=0;i<31;i++){
for(j=1;j<=n;j++){
v[j]=(b[j]>>i)&1;
}
(ans+=cal()*(1<<i))%=MOD;
}
printf("%lld\n",ans);
}
return 0;
}
/*
3
1
61
5
1 2 3 4 5
5
10187 17517 24636 19706 18756
*/
The end;