题目描述
难度分:1700
输入n(1≤n≤3×105)和长为n的数组a(0≤a[i]≤109)。
定义f(L,R)为连续子数组a[L]到a[R]的异或和。
计算所有f(L,R)×(R−L+1)的和,其中L≤R。
输出答案模998244353的结果。
输入样例1
3
1 3 2
输出样例1
12
输入样例2
4
39 68 31 80
输出样例2
1337
输入样例3
7
313539461 779847196 221612534 488613315 633203958 394620685 761188160
输出样例3
257421502
算法
按位贡献
由于n≤3×105,肯定不能枚举所有的子数组,统计所有子数组的贡献,因为子数组的数量就有O(n2)的级别。
先考虑一个比较暴力的做法,枚举子数组的右端点r∈[1,n],此时对于一个合法的子数组[l,r],它对答案的贡献就应该是s=Σl∈[1,r]f(l,r)×(r−l+1)。如果预处理出一个前缀和数组eor,eor[i]=⊕ik=1a[i],则f(l,r)=eor[r]⊕eor[l−1],s就可以化简为
s=Σl∈[1,r]eor[r]⊕eor[l−1]×(r−l+1)
=Σl∈[0,r)eor[r]⊕eor[l]×(r−l)。
=r×Σl∈[0,r)eor[r]⊕eor[l]−Σl∈[0,r)eor[r]⊕eor[l]×l
此时我们希望当遍历到r时,可以把所有以a[r]结尾的子数组贡献都在常数时间O(1)下计算出来。所以我们希望知道有多少个eor[l]和eor[r]异或之后可以不为0,但是如果不拆位,eor[i]的值域就会非常庞大,无法计算,因此我们需要按位预处理出每一个位b的前缀异或和数组,根据题面所给的a[i]范围,可以开31位(0~30)。此时对于某一位b,有贡献值
sb=2b×r×Σl∈[0,r)eor[b][r]⊕eor[b][l]−2b×Σl∈[0,r)eor[b][r]⊕eor[b][l]×l
当遍历r∈[1,n]时,维护l∈[0,r)中,前缀异或eor[b][l]为0或1时的频数,记录在cnt数组中,索引之和记录在sum数组中,这样sb就可以化为
sb=2b×(r×cnt[1−eor[b][r]]−sum[1−eor[b][r]])
如此一来,就可以O(1)计算出每个r∈[1,n]在各个二进制位上的贡献了。外层循环遍历位b∈[0,30],内层循环枚举子数组的右端点r∈[1,n],计算该位的贡献。
复杂度分析
时间复杂度
预处理出每一位b的前缀异或和数组,时间复杂度为O(30n)。接下来按位统计每个r∈[1,n]的贡献,统计每一位的贡献时间复杂度也是线性的,因此时间复杂度还是O(30n)。整个算法的时间复杂度为O(n),可以看成有个比较大的常数A,其中A=log2(maxi∈[1,n]a[i])。
空间复杂度
空间消耗的瓶颈主要就在于每一位的前缀异或数组eor,每一位的空间都是O(n)的,由于实现时是全局变量,根据题目的数据范围一共有30位。而理论上整个算法的额外空间复杂度为O(n),也可以看成有个比较大的常数A,其中A=log2(maxi∈[1,n]a[i])。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 300010, MOD = 998244353;
int n, a[N];
LL eor[31][N];
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
for(int b = 0; b <= 30; b++) {
for(int i = 1; i <= n; i++) {
eor[b][i] = eor[b][i - 1] ^ (a[i]>>b&1);
}
}
LL ans = 0;
for(int b = 0; b <= 30; b++) {
LL cnt[2] = {0}, sum[2] = {0};
cnt[0] = 1;
for(int r = 1; r <= n; r++) {
LL bv = eor[b][r];
if(cnt[1 - bv] > 0) {
ans = (ans + r*cnt[1 - bv]%MOD*(1ll<<b)%MOD) % MOD;
ans = (ans - sum[1 - bv]*(1ll<<b)%MOD + MOD) % MOD;
}
cnt[bv]++;
sum[bv] = (sum[bv] + r) % MOD;
}
}
printf("%lld\n", ans);
return 0;
}