解法:考虑枚举这个区间,设这个区间异或和为w。对于左端点 ,预处理出其左侧的所有子区间异或和的和 ,表示区间上所有子区间异或和。对右侧也做同样操作预处理出,表示区间上所有子区间异或和。那么最后答案就是。最重要的就是如何求pre、suf数组,以及如何枚举,答案就是通过拆位技巧得到,所谓拆位技巧就是按位计算。
pre数组的处理过程:定义s[i]表示前i个数异或和,则s[i]的二进制数第k位表示前i个数的二进制数第k位异或和,预处理好的。以拆位视角,仅考虑第 k个二进制位的贡献。对于一个右端点 i 确定的区间,考虑二进制第k位,该区间在该二进制位上贡献为1的次数等于:s[i]的第k位不等于s[x-1]的第k位,并且的x的个数。在区间上,讨论i对的贡献,就相当于i固定去找x的个数。
suf数组处理过程类似。
枚举过程:也是通过拆位技巧实现,考虑第k位的贡献。此时s[i]设置成后i个数的异或和,对于区间,先枚举左端点,右端点位置就是去区间上找一个x,使得上第k位异或和为1的x的个数,跟上面类似等于:的第k位不等于的第k位的个数。
代码具体如下:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const long long mod = 998244353;
ll a[200005];
ll pre[200005];//前缀子区间和
ll suf[200005];//后缀子区间和
ll s[200005];//异或和
ll f[3][205];//f[0/1][j]表示第j位是0/1的个数.
int main()
{
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
s[i] = s[i - 1] ^ a[i];
}
//处理前缀
memset(f, 0, sizeof(f));
//0的个数要初始化位1。
for (int i = 0; i <= 30; i++)
{
f[0][i] = 1;
}
for (int i = 1; i <= n; i++)
{
pre[i] = pre[i - 1];
for (int j = 0; j <= 30; j++)
{
int u = (s[i] >> j) & 1;//取s[i]的第j位
pre[i] = (pre[i] + (1 << j) * f[u ^ 1][j]%mod)%mod;//u=0就去找1的个数,u=1就去找0的个数,再乘上第j位十进制值
}
for (int j = 0; j <= 30; j++)
{
int u = (s[i] >> j) & 1;
f[u][j]++;//依次更新0/1的个数
}
}
//处理后缀
memset(f, 0, sizeof(f));
for (int i = n; i >= 1; i--)
{
s[i] = s[i + 1] ^ a[i];
}
for (int i = 0; i <= 30; i++)
{
f[0][i] = 1;
}
for (int i = n; i >= 1; i--)
{
suf[i] = suf[i + 1];
for (int j = 0; j <= 30; j++)
{
int u = (s[i] >> j) & 1;
suf[i] = (suf[i] + (1 << j) * f[u ^ 1][j]%mod)%mod;
}
for (int j = 0; j <= 30; j++)
{
int u = (s[i] >> j) & 1;
f[u][j]++;
}
}
//最后解决
memset(f, 0, sizeof(f));
//此时f[0/1][j]记录的是第j位为有k个0/1的k个suf数组的总和
ll ans = 0;
for (int i = n; i >= 1; i--)
{
for (int j = 0; j <= 30; j++)
{
int u = (s[i] >> j) & 1;
ans = (ans + pre[i - 1] * (1 << j) % mod * f[u ^ 1][j])%mod;//pre*中间异或和*suf
}
for (int j = 0; j <= 30; j++)
{
int u = (s[i] >> j) & 1;
f[u][j] = (f[u][j] + suf[i])%mod;
}
}
cout << ans << endl;
return 0;
}