区间专题(一)、分块及莫队算法

邀请赛之前可能只会更这一次了吧QAQ

久闻莫队算法的大名,号称是“可以解决任何区间问题”的算法,今天就来稍微说一下莫队算法。

这个算法是一位名叫莫涛的国家队队长发明的算法,所以尊称为莫队算法

莫队的原版文章里面的题目有一定难度,所以可以先看一下这道例题:

Description

    有n个数字,给出k,以及m个查询。

    每次查询的格式是L,r,求L~r(左右包含)这个区间内数字的出现次数刚好是k的数字种数。

    范围:n<=30000,k<=n,m<=30000,1<=L<r<=n,数列中元素大小<=n。

    输入n,k,m,然后n个元素,然后m行查询,对于每一个询问,输出正确的答案。

Example input:

5 2 3

1 2 3 2 2

1 2

2 4

1 5

Example output:

0

1

0

关于这道题,最直观的感觉就是暴力大法好。对于每一次询问,我们都遍历L~r的所有数字,并记录出现次数为k的数字。这样的话复杂度是O(n*m)的,肯定是过不了的。

再来看一下MyZhY提出的一种方法。

设置两个指针left和right,分别指向被查询的区间左右端点L,r,然后将这两个指针不断地移动到下一个待查区间,每次移动的时候把移动的位置上的数字出现次数+1。这么做当然是可以的,但最坏情况下,每次询问移动的大小仍有可能是n次,复杂度依旧没有改变,甚至可能更慢。

但是莫队算法就是基于这种思想实现的。莫队首先给这个序列分块。什么是分块?顾名思义就是把一个完整的序列分割成若干个大小近似相同的块,维护这些块的性质就可以了。分块的时候一般是分成√n(向上取整)个块(除了最后一个块可能不完整),这样每个块里的元素近似等于√n了。我们给这些块按左端点L排序,每个块内部按右端点排序,这样一来就会有神奇的事情发生。我们仍然考虑以上的双指针思路,并分成几种情况:

1、待查询的区间完全包含在一个块内。这样直接遍历块内的所有元素就可以了,由于最多√n个元素,所以left指针最多移动√n次,而right指针则未知,最多移动n次,故复杂度是O(n√n)

2、待查询的区间跨越了不同块。则right最多也是移动√次,由于有n个块,所以最终的复杂度也是O(n√n),而left在每一次询问下也最多移动√n次,共m√n次。

由此,该算法的总复杂度为O((m+n)√n),足以应付大部分的情况了。

总结一下,莫队算法的用途是处理一系列离线的,一般是不带修改的区间查询问题。带修改的莫队的话则需要把二元组[left,right]变成三元组[left,right,x],代表询问left到right这个区间在经过x次修改后的答案。另外还有一些什么树上莫队,草丛莫队(提莫?)之类的骚操作我也不会QAQ

今天的例题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
Copy
6 2 3
1 2 1 1 0 3
1 6
3 5
output
Copy
7
0
input
Copy
5 3 1
1 1 1 1 1
1 5
2 4
1 3
output
Copy
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.

题意:给你一串数字,每次问你某个区间的所有子区间中异或和为k的有多少。

分析:可以看作莫队的模板题,我们先求区间的异或前缀和xor[],然后对前缀和做莫队算法。根据异或的性质,xor[l-1]^xor[r]==k等价于k^xor[r]=xor[l-1]或者xor[l-1]^k=xor[r].某区间[i,j]的异或和就是xor[i-1]^xor[j].

代码如下:

#include <cmath>
#include <queue>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#define LL long long
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 2e6+7;
struct node {int l,r,id;}q[maxn];
int a[maxn],pos[maxn],flag[maxn];
LL ans[maxn],sum[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))
    {
        Ans=0;memset(flag,0,sizeof(flag));
        int ss=sqrt(n);flag[0]=1;
        for(i=1;i<=n;i++) scanf("%d",a+i),a[i]^=a[i-1],pos[i]=i/ss;
        for(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(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(i=1;i<=m;i++) printf("%I64d\n",ans[i]);
    }
    return 0;
}


展开阅读全文

没有更多推荐了,返回首页