bzoj 4017: 小Q的无敌异或(线段树)

4017: 小Q的无敌异或

Time Limit: 20 Sec  Memory Limit: 128 MB
Submit: 694  Solved: 227
[ Submit][ Status][ Discuss]

Description

背景
小Q学习位运算时发现了异或的秘密。
描述
小Q是一个热爱学习的人,他经常去维基百科(http://en.wikipedia.org/wiki/Main_Page)学习计算机科学。
就在刚才,小Q认真地学习了一系列位运算符(http://en.wikipedia.org/wiki/Bitwise_operation),其中按位异或的运算符 xor 对他影响很大。按位异或的运算符是双目运算符。按位异或具有交换律,即i xor j = j xor i。
他发现,按位异或可以理解成被运算的数字的二进制位对应位如果相同,则结果的该位置为0,否则为1,例如1(01) xor 2(10) = 3(11)。
他还发现,按位异或可以理解成数字的每个二进制位进行了不进位的加法,例如3(11) xor 3(11) = 0(00)。
于是他想到了两个关于异或的问题,这两个问题基于一个给定的非负整数序列A1, A2, ..., An,其中n是该序列的长度。
第一个问题是,如果用f(i, j)表示Ai xor Ai+1 xor ... xor Aj,则任意的1 <= i <= j <= n的f(i, j)相加是多少。
第二个问题是,如果用g(i, j)表示Ai + Ai+1 + ... + Aj,则任意的1 <= i <= j <= n的g(i, j)异或在一起是多少。
比如说,对于序列{1, 2},所有的f是{1, 2, 1 xor 2},加起来是6;所有的g是{1, 2, 1 + 2},异或起来是0。
他觉得这两个问题都非常的有趣,所以他找到了你,希望你能快速解决这两个问题,其中第一个问题的答案可能很大,你只需要输出它对998244353(一个质数)取模的值即可。

Input

第一行一个正整数n,表示序列的长度。
第二行n个非负整数A1, A2, ..., An,表示这个序列。

Output

两个整数,表示两个问题的答案,空格隔开,其中第一个问题的答案要对998244353(一个质数)取模。

Sample Input

1 2

Sample Output

6 0


第一问:求出所有区间异或和的和

直接按位算贡献,如果一个区间中1的个数为奇数,那么这个区间对答案就有贡献

这个直接用前缀和就可以搞定了

第二问:求出所有区间和的异或和

先求出前缀和,假设区间[x, y]满足(sum[y]-sum[x-1])%2^(k+1)>=2^k,这样的区间总共有奇数个,那么答案的第k位就为1,否则就为0,将所有的sum[]对2^(k+1)取模后,如果存在x, y(x<y)满足

sum[y]-sum[x]>=2^k或者sum[y]-sum[x]>=-(2^k)&&sum[y]-sum[x]<0,那么区间[x, y]就满足上述条件,这个用树状数组就可以解决


#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define mod 998244353
#define LL long long
int n, a[100005], tre[100005];
LL sum[100005], er[55] = {1};
typedef struct Res
{
	LL x;
	int id;
	bool operator < (const Res &b) const
	{
		if(x<b.x || x==b.x && id<b.id)
			return 1;
		return 0;
	}
}Res;
Res s[100005];
int Query(int x)
{
	int ans = 0;
	while(x)
	{
		ans += tre[x];
		x -= x&-x;
	}
	return ans;
}
void Update(int k, int x)
{
	while(k<=n)
	{
		tre[k] += x;
		k += k&-k;
	}
}
int main(void)
{
	LL ans;
	int i, j, A, B, now, p;
	//freopen("in.txt", "r", stdin);
	scanf("%d", &n);
	for(i=1;i<=n;i++)
		scanf("%d", &a[i]);
	for(i=1;i<=45;i++)
		er[i] = er[i-1]*2;
	ans = 0;
	for(i=0;i<=20;i++)
	{
		A = 1, B = 0;
		now = 0;
		for(j=1;j<=n;j++)
		{
			if(a[j]&(1<<i))
				now ^= 1;
			if(now==1)
			{
				ans = (ans+(LL)A*er[i])%mod;
				B++;
			}
			else
			{
				ans = (ans+(LL)B*er[i])%mod;
				A++;
			}
		}
	}
	printf("%lld", ans);
	for(i=1;i<=n;i++)
		sum[i] = sum[i-1]+a[i];
	ans = 0;
	for(i=0;i<=40;i++)
	{
		for(j=1;j<=n;j++)
			s[j].x = sum[j]%er[i+1], s[j].id = j;
		sort(s+1, s+n+1);
		p = 1, now = 0;
		memset(tre, 0, sizeof(tre));
		for(j=1;j<=n;j++)
		{
			while(s[p].x+er[i]<=s[j].x && p<=j)
				Update(s[p++].id, 1);
			now = (now+Query(s[j].id))%2;
			if(s[j].x>=er[i])
				now ^= 1;
		}
		memset(tre, 0, sizeof(tre));
		p = 1;
		for(j=1;j<=n;j++)
		{
			while(s[p].x+er[i]<s[j].x && p<=j)
				Update(s[p++].id, -1);
			now = (now+(Query(n)-Query(s[j].id)))%2;
			Update(s[j].id, 1);
		}
		if(now)
			ans += er[i];
	}
	printf(" %lld\n", ans);
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值