莫队算法入门 Codeforces617E

莫队算法:

莫队算法的用处是,对于一个区间内的查询,当我们已经知道了 [ L , R ] 的答案的时候,有莫队算法可以在很短的时间内得到 [ L - 1 , R ] 或者是 [ L ,R + 1 ]的答案,前提是可以离线处理。
步骤:
1.输入 N 个点的时候,对每个点的进行分块 ,一般 N 个点分成 sqrt(n) 块 ,用一个pos[]数组记录分块
int s = sqrt(n);
for(int i = 1;i <= n;i++){
	scanf("%d",&a[i]);
	a[i] ^= a[i - 1];	
	pos[i] = i / s;		//记录所在分块 
}
2.输入m个询问,将它保存下来之后,对它进行排序,按照询问的左端点所在块排序,同块则按右端点排序,都是从小到大
bool cmp(node a,node b){
	if(pos[a.l] != pos[b.l])	//两个询问的左极限如果在不同块,那么按分块顺序从小到大,否则按右极限 
		return pos[a.l] < pos[b.l];
	return a.r < b.r;
}
3.开始处理询问,从前往后,一次挪动处理,由于有了前面的排序,后面的移到次数被大大减少了,所以时间很短
然后一边记录答案,最后输出就好。
最简单的例题 : Codeforces 617E
E. XOR and Favorite Number
time limit per test
4 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

Bob has a favorite number k and ai of length n. Now he asks you to answer m queries. Each query is given by a pair li and ri and asks you to count the number of pairs of integers i and j, such that l ≤ i ≤ j ≤ r and the xor of the numbers ai, ai + 1, ..., aj is equal to k.

Input

The first line of the input contains integers nm and k (1 ≤ n, m ≤ 100 0000 ≤ k ≤ 1 000 000) — the length of the array, the number of queries and Bob's favorite number respectively.

The second line contains n integers ai (0 ≤ ai ≤ 1 000 000) — Bob's array.

Then m lines follow. The i-th line contains integers li and ri (1 ≤ li ≤ ri ≤ n) — the parameters of the i-th query.

Output

Print m lines, answer the queries in the order they appear in the input.

Examples
input
6 2 3
1 2 1 1 0 3
1 6
3 5
output
7
0
input
5 3 1
1 1 1 1 1
1 5
2 4
1 3
output
9
4
4
Note

In the first sample the suitable pairs of i and j for the first query are: (12), (14), (15), (23), (36), (56), (66). Not a single of these pairs is suitable for the second query.

In the second sample xor equals 1 for all subarrays of an odd length.


题意:给出一个数组,下标从 1 到 n ,有 m 次询问,一个值 k
每次询问给出一个区间范围,问区间范围内连续异或值为 k 的子区间有多少个

思路:区间问题,并且可以离线处理,所以莫队是最好选择。
首先要考虑异或的特性  x ^ k = 0 表示x = k ,所以我们可以把区间里的异或值拿去 异或 k,结果为 0 就符和条件
还有就是 如果前面出现异或结果为 y ,而你当前的x ^ k = y 的话,那么表示 x ^ y = k,也满足条件。
还有就是题目要求的是连续的子区间,所以我们得做前缀和才方便处理 . 前缀数组 a[]

开始处理询问,对于每个询问,用 add() 函数 和 dele() 函数去移动到对应的位置
void add(int x){
	Ans += flag[a[x] ^ k];	
	flag[a[x]]++;
}
Ans记录符合子区间个数,flag[x] 存的是已有异或值为x 的区间的个数 ,flag[0] = 1 ,因为x ^ k = 0 表示 x = k
然后 flag 记数 异或值区间 ,当你下一次的异或值 X去 异或 K = Y,而flag[Y]又有值的时候,表示有 flag[Y] 个区间去异或 X 可以等于 K ,表示 [1,a] ^ [1,b] = k ,也就是说 [b,a] = k,又有 flag[Y] 个区间是满足条件,所以计数可以增加flag[y]个。
void dele(int x){
	flag[a[x]]--;
	Ans -= flag[a[x] ^ k];
}
dele数组就比较简单了,减去它的贡献就好了。
a[x] ^ k = z ,所以 a[x] ^ z = k,减去了 a[x],那么原本 有 flag[z]个区间是满足的,得把它减掉

注意补充:在对左指针进行操作的时候,比如我左指针要右移一位,那么就是要删除当前所在位置的数的贡献值
但是,我们并不能 dele(l++) 而是 dele(l - 1) , l++;
原因是,我们是做了一个 异或前缀和 a[ l ] = x1 ^ x2 ^ ... ^xl ,那么 如果要单找 xl 的话,不是直接 a[ l ] 可以得到的
a[ l ] = a[ l ] ^ a[ l - 1 ] ,也就是说 xl 的贡献是由 a[ l - 1 ] 得来的,所以当我们要删去 x l 的贡献的时候,我们需要dele 的是 a[ l - 1 ] 。
同理,左移的时候要先 l -- 移动到 位置,然后 add(l - 1) 才能把 l 的贡献增加上去
AC代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
#define maxn 2000006
#define mem(a,x) memset(a,x,sizeof(a))
#define ll long long

struct node{ //记录询问,离线处理 
	int l,r,id;
}q[maxn];

int a[maxn],pos[maxn];	// a是前缀数组,pos用于分块 
ll ans[maxn],sum[maxn],flag[maxn];
int n,m,k;
ll Ans = 0;
bool cmp(node a,node b){
	if(pos[a.l] != pos[b.l])	//两个询问的左极限如果在不同块,那么按分块顺序从小到大,否则按右极限 
		return pos[a.l] < pos[b.l];
	return a.r < b.r;
}
void add(int x){
	Ans += flag[a[x] ^ k];	
	flag[a[x]]++;
}
void dele(int x){
	flag[a[x]]--;
	Ans -= flag[a[x] ^ k];
}
int main(){
	int i;
	while(scanf("%d %d %d",&n,&m,&k) != EOF){
		Ans = 0;
		mem(flag,0);
		int s = sqrt(n); //有n个点,分成 sqrt(n)个块 
		flag[0] = 1; // x 异或 k 之后等于0表示 x 等于 k,记录一个贡献 
		for(int i = 1;i <= n;i++){
			scanf("%d",&a[i]);
			a[i] ^= a[i - 1];	//记录异或前缀和 
			pos[i] = i / s;		//记录所在分块 
		}
		for(int i = 1;i <= m;i++){
			scanf("%d %d",&q[i].l,&q[i].r);
			q[i].id = i;
		}
		sort(q + 1,q + 1 + m,cmp);
		int l = 1,r = 0;
		for(int i = 1;i <= m;i++){ //对于每个查询 
			while(q[i].l < l){ //把左右极限移到对应位置 
				l--;
				add(l - 1);
			}
			while(q[i].l > l){
				dele(l - 1);
				l++;
			}
			while(q[i].r < r){
				dele(r);
				r--;
			}
			while(q[i].r > r){
				r++;
				add(r);
			}
			ans[q[i].id] = Ans;
		}
		for(int i = 1;i <= m;i++)
			printf("%lld\n",ans[i]);
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值