莫隊演算法入門 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
The first line of the input contains integers n
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.
OutputPrint m lines, answer the queries in the order they appear in the input.
Examples input6 2 3 1 2 1 1 0 3 1 6 3 5output
7 0input
5 3 1 1 1 1 1 1 1 5 2 4 1 3output
9 4 4Note
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.
題意:給出一個數組,下標從 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;
}