2023牛客暑假多校-5-I-The Yakumo Family

 解法:考虑枚举[l_{2},r_{2}]这个区间,设这个区间异或和为w。对于左端点 ,预处理出其左侧的所有子区间异或和的和pre[i] ,表示区间[1,i]上所有子区间异或和。对右侧也做同样操作预处理出suf[i],表示区间[i,n]上所有子区间异或和。那么最后答案就是\sum w*pre[l_{2}-1]*suf[r_{2}+1]。最重要的就是如何求pre、suf数组,以及如何枚举,答案就是通过拆位技巧得到,所谓拆位技巧就是按位计算。

pre数组的处理过程:定义s[i]表示前i个数异或和,则s[i]的二进制数第k位表示前i个数的二进制数第k位异或和,预处理好的。以拆位视角,仅考虑第 k个二进制位的贡献。对于一个右端点 i 确定的区间[x,i],考虑二进制第k位,该区间在该二进制位上贡献为1的次数等于:s[i]的第k位不等于s[x-1]的第k位,并且x\leq i的x的个数。在区间[1,i]上,讨论i对pre[i]的贡献,就相当于i固定去找x的个数。

suf数组处理过程类似。

枚举过程:也是通过拆位技巧实现,考虑第k位的贡献。此时s[i]设置成后i个数的异或和,对于区间[l_{2},r_{2}],先枚举左端点,右端点位置就是去区间[l_{2},n]上找一个x,使得[l_{2},x]上第k位异或和为1的x的个数,跟上面类似等于:s[l_{2}]的第k位不等于s[x+1]的第k位的个数。

代码具体如下:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const long long mod = 998244353;
ll a[200005];
ll pre[200005];//前缀子区间和
ll suf[200005];//后缀子区间和
ll s[200005];//异或和
ll f[3][205];//f[0/1][j]表示第j位是0/1的个数.
int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		s[i] = s[i - 1] ^ a[i];
	}
	//处理前缀
	memset(f, 0, sizeof(f));
	//0的个数要初始化位1。
	for (int i = 0; i <= 30; i++)
	{
		f[0][i] = 1;
	}
	for (int i = 1; i <= n; i++)
	{
		pre[i] = pre[i - 1];
		for (int j = 0; j <= 30; j++)
		{
			int u = (s[i] >> j) & 1;//取s[i]的第j位
			pre[i] = (pre[i] + (1 << j) * f[u ^ 1][j]%mod)%mod;//u=0就去找1的个数,u=1就去找0的个数,再乘上第j位十进制值
		}
		for (int j = 0; j <= 30; j++)
		{
			int u = (s[i] >> j) & 1;
			f[u][j]++;//依次更新0/1的个数
		}
	}
	//处理后缀
	memset(f, 0, sizeof(f));
	for (int i = n; i >= 1; i--)
	{
		s[i] = s[i + 1] ^ a[i];
	}
	for (int i = 0; i <= 30; i++)
	{
		f[0][i] = 1;
	}
	for (int i = n; i >= 1; i--)
	{
		suf[i] = suf[i + 1];
		for (int j = 0; j <= 30; j++)
		{
			int u = (s[i] >> j) & 1;
			suf[i] = (suf[i] + (1 << j) * f[u ^ 1][j]%mod)%mod;
		}
		for (int j = 0; j <= 30; j++)
		{
			int u = (s[i] >> j) & 1;
			f[u][j]++;
		}
	}
	//最后解决
	memset(f, 0, sizeof(f));
	//此时f[0/1][j]记录的是第j位为有k个0/1的k个suf数组的总和
	ll ans = 0;
	for (int i = n; i >= 1; i--)
	{
		for (int j = 0; j <= 30; j++)
		{
			int u = (s[i] >> j) & 1;
			ans = (ans + pre[i - 1] * (1 << j) % mod * f[u ^ 1][j])%mod;//pre*中间异或和*suf
		}
		for (int j = 0; j <= 30; j++)
		{
			int u = (s[i] >> j) & 1;
			f[u][j] = (f[u][j] + suf[i])%mod;
		}
	}
	cout << ans << endl;
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值