题面:
定义一个数组的权值为数组中任意两个元素按位与(AND)值之和。具体来说,对于数组中的每个连续子数组,我们计算该子数组内所有可能的两个元素的按位与值,并将这些值相加。现在要求计算数组中所有的连续子数组的权值和,并将结果对 998244353 998244353 998244353 取模。
输入格式:
第一行输入一个整数( 2 ≤ n ≤ 1 0 5 2≤n≤10^5 2≤n≤105)表示数组的长度
第二行输入长度为 n n n 的数组 a a a( 1 ≤ a i ≤ 1 0 9 1\le a_i\le 10^9 1≤ai≤109)
样例:
输入样例:
6
1 1 4 5 1 4
输出样例:
92
思路:
首先有个比较容易想到的性质,就是两个数按位与的时候,不同的二进制位之间是互不影响的,所以我们可以把不同的二进制位分开来看,分别算出每一个二进制位的贡献再加起来。
对第 i i i 位二进制位,算它的贡献。正着来想,我们算某一个数组的权值,可以 O ( n ) O(n) O(n) 统计出这一位是 1 1 1 的个数 x x x,那么这个数组在这一位上的贡献就是 C x 2 ∗ 2 i C_x^2*2^i Cx2∗2i。但是数组的所有子数组太多,不可能一个一个算。
反过来想,不是从数组里统计有多少对 1 1 1,而是对每一对 1 1 1,计算有多少子数组包括它。 假设一对 1 1 1 分别在位置 i i i 和 j j j 上( i < j i\lt j i<j),那么就会有 i ∗ ( n − j + 1 ) i*(n-j+1) i∗(n−j+1) 个子数组包括它(左端点的取值有 i i i 个,右端点的取值有 n − j + 1 n-j+1 n−j+1 个)。
但是枚举左右两边的
1
1
1 的时间复杂度是
O
(
n
2
)
O(n^2)
O(n2) 的,会爆。发现固定一个左边的
1
1
1 的位置后,把后面的
1
1
1 依次当作右边的
1
1
1,分别计算贡献,它是满足乘法分配律的。比如 0100100100
,把第二个位置上的
1
1
1 看作是左边的
1
1
1,那么贡献是
2
∗
6
+
2
∗
3
=
2
∗
(
6
+
3
)
2*6+2*3=2*(6+3)
2∗6+2∗3=2∗(6+3)。
所以我们可以用个后缀和 s [ i ] s[i] s[i] 来处理出 i ∼ n i\sim n i∼n 里所有 1 1 1 到右端点 n n n 的距离和,那么对于第 i i i 位的 1 1 1,它作为左边的 1 1 1 的总贡献就是 i ∗ s [ i + 1 ] i*s[i+1] i∗s[i+1]。之后别忘了把每一个二进制位都算一遍就行了。
这题其实改成按位或,按位异或都可以做,不过按位与比较好做。枚举右边的 1 1 1,处理左边的 1 1 1 到左端点的距离的前缀和也是可以的,而且可能好写一点。
code:
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=1e5+5;
typedef long long ll;
const ll mod=998244353;
int n,a[maxn];
int s[35][maxn];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
for(int st=0;st<=30;st++)
s[st][i]=((a[i]>>st)&1)?n-i+1:0;
}
for(int st=0;st<=30;st++)
for(int i=n;i>=1;i--){
s[st][i]+=s[st][i+1];
s[st][i]%=mod;
}
ll ans=0;
for(int st=0;st<=30;st++){
for(int i=1;i<=n;i++){
if((a[i]>>st)&1){
ans+=1ll*i*s[st][i+1]%mod*(1<<st)%mod;
ans%=mod;
}
}
}
cout<<ans;
return 0;
}