『分块·莫队基础』[codeforces 617E]XOR and Favorite Number

博客围绕一个数列异或问题展开,给定长度为n的数列和正整数k,有m个询问,每次询问给定l、r,求满足条件的(i,j)个数。题解运用莫队算法,对询问分块排序,还说明了如何更新区间答案,以及k=0时删除操作的特殊处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

dydxh又没有强制在线,于是mzx又可以开心地水过了,不过他发现,在线貌似不可做?

给定长度为n的数列以及正整数,有m个询问每次询问给定两个数l,r,求l≤i≤j≤rl≤i≤j≤rlijr,且a(i)xora(i+1)xora(i+2)xor...xora(j)=k的(i,j)a(i) xor a(i+1) xor a(i+2) xor ... xor a(j)=k的(i,j)a(i)xora(i+1)xora(i+2)xor...xora(j)=k(i,j)的个数。

题解

根据莫队算法的原理,我们应该讲每一个询问进行分块。分块的方法是:将一个长度为nnn的序列分成n\sqrt nn块,若询问的左端点在相同的块上,就根据右端点从小到大排序;否则按左端点的块进行从小到大排序。

然后再回到此题,我们需要找到Sl xor Sr = kS_{l}\ xor\ S{r}\ =\ kSl xor Sr = k中有多少对(l,r)(l,r)(l,r).显然qi.l≤qi.rq_i.l\leq q_i.rqi.lqi.r.SiS_iSi表示原序列中前iii个数的异或和。

假设我们已经知道了(l,r)(l,r)(l,r)的答案,我们如何更新其它的答案呢:

  • 更新区间(l+1,r)(l+1,r)(l+1,r),那么合法的左区间位置由[l−1,r−1][l-1,r-1][l1,r1]变成了[l,r][l,r][l,r],因此减去第l−1l-1l1个位置的影响即可。
  • 跟新区间(l−1,r)(l-1,r)(l1,r),那么合法的左区间位置由[l−1,r−1][l-1,r-1][l1,r1]变成了[i−2,r−1][i-2,r-1][i2,r1],添加第l−2l-2l2个位置的影响即可。
  • 右区间的更新方式同理。

还有一个值得注意的地方:当k=0k=0k=0且在执行某一个删除操作的时候,会多减一个自己本身产生的影响;所以每一次进行删除操作得时候,便需要重新对答案进行+1+1+1操作才行。

代码如下:

#include <bits/stdc++.h>
#define int long long
using namespace std;

int n,m,k,T;
struct node
{
	int l,r,id,p;
	friend bool operator < (node p1, node p2) {
		if (p1.p == p2.p) return p1.r < p2.r;
		return p1.p < p2.p;
	}
} q[200000];
int ans = 0;
int a[200000];
int Ans[200000];
int cnt[2000000];

inline int read(void)
{
	int s = 0, w = 1;char c = getchar();
	while (c<'0' || c>'9') {if (c == '-') w = -1; c = getchar();}
	while (c>='0' && c<='9') s = s*10+c-48,c = getchar();
	return s*w;
}

void init(void)
{
	n = read(), m = read(), k = read(), T = sqrt(n);
	for (int i=1;i<=n;++i) a[i] = read(), a[i] = a[i-1] ^ a[i];
	for (int i=1;i<=m;++i) 
	{
		q[i].l = read();
		q[i].r = read();
		q[i].id = i;
		q[i].p = (q[i].l + T - 1) / T;//表示第i个询问所属的块的编号
	}
	sort(q+1, q+m+1);
	return;
}

void change(int x,int del)
{
	ans += cnt[x ^ k] * del;
	cnt[x] += del;
	if (del == -1 && k == 0) ans ++;
	return;
}

void work(void)
{
	int L , R;
	L = R = 1;
	cnt[a[1]] ++;cnt[0] ++;
	ans = (a[1] == k);
	for (int i=1;i<=m;++i)
	{
		while (L < q[i].l) change(a[L-1],-1), L ++;
		while (L > q[i].l) L --, change(a[L-1],1);
		while (R > q[i].r) change(a[R],-1), R --;
		while (R < q[i].r) R ++, change(a[R],1);
		Ans[q[i].id] = ans;
	}
	for (int i=1;i<=m;++i) printf("%lld\n", Ans[i]);
	return;
}

signed main(void)
{
	freopen("2011.in","r",stdin);
	freopen("2011.out","w",stdout);
	init();	
	work();
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值