题目大意
给定一段长度为
n
(
3
≤
n
≤
2
∗
1
0
5
)
n(3 \leq n \leq 2*10^5)
n(3≤n≤2∗105) 的数组
a
(
0
≤
a
i
≤
1
0
9
)
a\ (0\leq a_i \leq 10^9)
a (0≤ai≤109),求
∑
1
≤
l
1
≤
r
1
≤
l
2
≤
r
2
≤
l
3
≤
r
3
X
O
R
(
l
1
,
r
1
)
∗
X
O
R
(
l
2
,
r
2
)
∗
X
O
R
(
l
3
,
r
3
)
\sum_{1\leq l_1\leq r_1\leq l_2\leq r_2\leq l_3\leq r_3}XOR(l_1,r_1)*XOR(l_2,r_2)*XOR(l_3,r_3)
∑1≤l1≤r1≤l2≤r2≤l3≤r3XOR(l1,r1)∗XOR(l2,r2)∗XOR(l3,r3)
定义
X
O
R
(
l
,
r
)
XOR(l,r)
XOR(l,r) 为数组中
a
l
a_l
al 到
a
r
a_r
ar 的异或和。
结果取模
998244353
998244353
998244353
题解
思维
运用拆位,考虑转化为二进制后的每一位。
①求解
∑
1
≤
l
1
≤
r
1
≤
n
X
O
R
(
l
1
,
r
1
)
\sum_{1 \leq l_1\leq r_1\leq n}XOR(l_1,r_1)
∑1≤l1≤r1≤nXOR(l1,r1)
显然的,求解有多少段区间异或和为
1
1
1 ,
令右端点固定,枚举左端点。
由于异或的性质,我们可以通过前缀异或和判断一段区间的异或和。
定义
p
r
e
i
pre_i
prei表示异或和
我们发现只有左端点和右端点的
p
r
e
pre
pre值不同才能做出贡献。添加一个数组
s
u
m
i
.
,
0
/
1
sum_{i.,0/1}
sumi.,0/1 ,统计前面的
p
r
e
pre
pre 值,我们就可以在
O
(
n
)
O(n)
O(n) 的复杂度内求出结果。
·②求解
∑
1
≤
l
1
≤
r
1
≤
l
2
≤
r
2
≤
n
X
O
R
(
l
1
,
r
1
)
∗
X
O
R
(
l
2
,
r
2
)
\sum_{1 \leq l_1\leq r_1\leq l_2 \leq r_2\leq n}XOR(l_1,r_1)*XOR(l_2,r_2)
∑1≤l1≤r1≤l2≤r2≤nXOR(l1,r1)∗XOR(l2,r2)
同样的,只有区间异或和为
1
1
1 的区间才能做出贡献。
我们需要维护有多少①中的区间前面还有一个不重叠的区间。在求解①的答案时,我们利用数组,计算有几个区间异或和为1的右端点在它前面。
我们发现,它又可以用一个前缀和数组来求出当前值。
③维护完了前两个数组,我们发现可以实现套娃操作,每次从上一个数组值中完成累加。
实现的一些优化
我们可以通过三维数组维护操作数、数位、当前位,三重循环求解
过程中转移是定向的,所以可以压缩一些数组。
参考代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+5,M=30;
const int mod=998244353;
ll s[N];
ll a[N],f[N];
int n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
ll x;
scanf("%lld",&x);
a[i]=a[i-1]^x;
s[i]=1;
}
for(int k=1;k<=3;k++)
{
for(int j=0;j<=M;j++) //拆位
{
ll c[2]={0,0}; //在前缀和为0/1前能放k个区间
if(k==1)
c[1]=1;
for(int i=1;i<=n;i++)
{
f[i]=(f[i]+(c[((a[i]>>j)&1)^1]<<j))%mod; //统计方案数
c[(a[i]>>j)&1]=(c[(a[i]>>j)&1]+s[i])%mod;
}
}
for(int i=1;i<=n;i++)
{
s[i]=(s[i-1]+f[i])%mod; //滚动数组
f[i]=0;
// cout<<s[i]<<endl;
}
f[0]=0;
}
printf("%lld",s[n]);
}
总结
将问题分解开来,发现小问题之间的联系,再通过关系快速求解。