区间权值和(计算贡献)

题面:

定义一个数组的权值为数组中任意两个元素按位与(AND)值之和。具体来说,对于数组中的每个连续子数组,我们计算该子数组内所有可能的两个元素的按位与值,并将这些值相加。现在要求计算数组中所有的连续子数组的权值和,并将结果对 998244353 998244353 998244353 取模。

输入格式:

第一行输入一个整数( 2 ≤ n ≤ 1 0 5 2≤n≤10^5 2n105)表示数组的长度

第二行输入长度为 n n n 的数组 a a a 1 ≤ a i ≤ 1 0 9 1\le a_i\le 10^9 1ai109

样例:

输入样例:
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 Cx22i。但是数组的所有子数组太多,不可能一个一个算。

反过来想,不是从数组里统计有多少对 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(nj+1) 个子数组包括它(左端点的取值有 i i i 个,右端点的取值有 n − j + 1 n-j+1 nj+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) 26+23=2(6+3)

所以我们可以用个后缀和 s [ i ] s[i] s[i] 来处理出 i ∼ n i\sim n in 里所有 1 1 1 到右端点 n n n 的距离和,那么对于第 i i i 位的 1 1 1,它作为左边的 1 1 1 的总贡献就是 i ∗ s [ i + 1 ] i*s[i+1] is[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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值