Codeforces Round #340 (Div. 2) E题 莫队算法

E. XOR and Favorite Number
time limit per test4 seconds
memory limit per test256 megabytes
inputstandard input
outputstandard 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 n, m and k (1 ≤ n, m ≤ 100 000, 0 ≤ 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.

Sample test(s)
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: (1, 2), (1, 4), (1, 5), (2, 3), (3, 6), (5, 6), (6, 6). 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值。
题解:
首先,对于原本的序列我们用a[i]来表示前i个数异或的结果。当我们要求[L,R]这个区间的数的异或结果时。只需要a[R]^a[L-1]。

对于有多个询问区间的不可修改的问题。莫队算法是一种比较通用的解决思路。

莫队算法的思想是充分利用已知信息来高效解决问题。主
要动作是调整询问顺序的离线操作。

比如,我们有q1,q2,q3,q4四个询问。那么我们可不可以在解决q2时用到q1求出的结论?能不能在解决q3时用到q2的结论?比如区间q1的询问是求[L1,R1]的结论,那么我们能否在很短的时间内(例如O(1))内求出[L1+1,R1](或者[L1-1,R1],[L1,R1+1][L1,R1-1])的结论。那么比如q2的询问是[L2,R2]从问题q1转移到q2的时间复杂度就是O(|L2-L1|+|R2-R1|),也就是q1和q2之间的曼哈顿距离。如果我们确定了合适的关系,使得q1,q2,q3,q4之间的曼哈顿距离和最小(即为曼哈顿最小生成树)。然后按照这个顺序求所有询问的解。速度就会大大提升。

但是在实际应用中,编写一个求曼哈顿生成树的程序比较复杂。不太合适。所以就有另一种替代方法。就是我们把总长为n的序列分成sqrt(n)块。然后把询问排序:如果两个询问L是在同一块的,那么就把R从小到大排,如果不同块。则按块编号从小到大。这样使得每个块里的询问曼哈顿距离相差不大,同一个块里有n^1/2个元素,每个元素最多耗时n^1/2的转移最多时间复杂度为O(n^1/2)*n^1/2 = O(n)。当在不同块转移时,最多的时间复杂度为O(n^3/2),所以最终时间复杂度是O(n^3/2)

具体到这个题目,就是如何让在O(1)时间内完成一次转移。我们可以数组来记录某一个值得出现次数,当某次要异或这个值时。直接加这个数的次数就可以了。

这题给的时间是4S,但是最终只用了0.4S就跑完了。可见威力

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define f(i,a,b) for(i = a;i<=b;i++)
#define fi(i,a,b) for(i = a;i>=b;i--)
#define m0(a) memset(a,0,sizeof(a))
#define mm(a) memset(a,0x3f,sizeof(a))


using namespace std;

struct PP{
    int _L,_R;
    int sign;
};

PP c[100000+5];
int N;
int a[100000+5];
long long ans[100000+5];
int p[1000000+5];

bool cmp1(PP _a,PP _b){
    if(_a._L/N==_b._L/N){
        return _a._R<_b._R;
    }else
        return _a._L/N<_b._L/N;
}

int main()
{
    ios_base::sync_with_stdio(false); cin.tie(0);
    a[0] = 0;
    int i,j;
    int n,m,k;
    scanf("%d%d%d",&n,&m,&k);
    f(i,1,n){
        scanf("%d",&a[i]);
        a[i]^=a[i-1];
    }

    int L,R;
    f(i,1,m){
        scanf("%d%d",&c[i]._L,&c[i]._R);
        c[i].sign = i;
    }
    N = (int)sqrt(n);
    sort(c+1,c+m+1,cmp1);

    L = 1,R = 0;
    long long sum = 0;
    p[0]++;
    f(i,1,m){
        while(Rwhile(R>c[i]._R){
            p[a[R]]--;
            sum-=p[a[R]^k];
            R--;
        }
        while(L1]]--;
            sum-=p[a[L-1]^k];
            L++;
        }
        while(L>c[i]._L){
            L--;
            sum+=p[a[L-1]^k];
            p[a[L-1]]++;
        }
        ans[c[i].sign] = sum;
    }
    f(i,1,m) printf("%I64d\n",ans[i]);

    return 0;
}

你可能感兴趣的:(其他)