洛谷 P9162
求方差只需要求出 \(\sum_{l=1}^n\sum_{r=l}^{n} f(l,r)\) 和 \(\sum_{l=1}^n\sum_{r=l}^{n} f(l,r)^2\)。因为第一个明显弱于第二个所以只考虑第二个怎么求。
考虑将贡献拆位,并将平方拆成两位的乘积。枚举两位 \(i,j\),设分别 \(b_k,c_k\) 表示 \(a_k\) 第 \(i,j\) 位的值,于是 \(f_{l,r}\) 在第一位为 \(i\) ,第二位为 \(j\) 时的贡献就是
\[((b_i⊕b_{i+1}⊕\cdots ⊕b_j)+(b_i∨b_{i+1}∨\cdots ∨b_j))\times((c_i⊕c_{i+1}⊕\cdots ⊕c_j)+(c_i∨c_{i+1}∨\cdots ∨c_j))
\]
分别对左右两个括号为 \(1/2,1/2\) 的四种情况计数即可。计数的方法可以维护最后一个 \(1\) 的位置和前缀和的奇偶性,这里不懂的可以看代码。
时间 \(O(n\log^2 V)\),空间 \(O(n)\)。
#include<bits/stdc++.h>
#define N 1001001
#define MAX 2001
#define inf 1e18
using namespace std;
typedef long long ll;
typedef long double db;
const ll mod=998244353,inv2=(mod+1)/2;
inline void read(ll &ret)
{
ret=0;char c=getchar();bool pd=false;
while(!isdigit(c)){pd|=c=='-';c=getchar();}
while(isdigit(c)){ret=(ret<<1)+(ret<<3)+(c&15);c=getchar();}
ret=pd?-ret:ret;
return;
}
ll n,a[N],sum,sum2,b[N],b1[N],b2[N],c[N][2][2];
signed main()
{
read(n);
for(int i=1;i<=n;i++)
read(a[i]);
for(int k=0;k<=31;k++)//和
{
for(int i=1;i<=n;i++)
b[i]=((a[i]>>k)&1);
ll res=0;
ll cnt0=0,cnt1=0;
cnt0++;
ll s=0;
ll las=0;
for(int r=1;r<=n;r++)
{
s+=b[r];
s&=1;
if(s==1)
res+=cnt0,cnt1++;
else
res+=cnt1,cnt0++;
if(b[r])
las=r;
res+=las;
res%=mod;
}
sum+=res*((1ll<<k)%mod)%mod;
sum%=mod;
sum-=sum>=mod?mod:0;
}
for(int k1=0;k1<=31;k1++)//平方和
{
for(int k2=0;k2<=31;k2++)
{
ll res=0;
for(int i=1;i<=n;i++)
b1[i]=(a[i]>>k1)&1,b2[i]=(a[i]>>k2)&1;
c[0][0][0]=1;
ll las1=0,las2=0,s1=0,s2=0;
for(int i=1;i<=n;i++)
{
memcpy(c[i],c[i-1],sizeof(c[i]));
if(b1[i])
las1=i;
if(b2[i])
las2=i;
s1+=b1[i],s2+=b2[i];
s1&=1,s2&=1;
c[i][s1][s2]++;
if(min(las1,las2)==0)
continue;
res+=1*1*(c[min(las1,las2)-1][s1][s2]);
res+=1*2*(c[min(las1,las2)-1][s1][s2^1]);
res+=2*1*(c[min(las1,las2)-1][s1^1][s2]);
res+=2*2*(c[min(las1,las2)-1][s1^1][s2^1]);
res%=mod;
}
sum2+=((1ll<<k1)%mod)*((1ll<<k2)%mod)%mod*res%mod;
sum2%=mod;
sum2-=sum2>=mod?mod:0;
}
}
printf("%lld\n",(sum2*n%mod*(n+1)%mod*inv2%mod-sum*sum%mod+mod)%mod);
exit(0);
}