给定一段长度为 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
#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<
}
f[0]=0;
}
printf("%lld",s[n]);
}
将问题分解开来,发现小问题之间的联系,再通过关系快速求解。