1. 程式人生 > >莫隊演算法 優雅的暴力 (Codeforences # 340 E div2)

莫隊演算法 優雅的暴力 (Codeforences # 340 E div2)

【解題前學習到的東西---莫隊演算法】

           在我現在粗淺的認知看來,莫隊演算法的主要精髓就是面對已知的詢問資訊,將這些詢問進行順序調整然後離線操作。

           例如對於多個詢問[l1,r1],[l2,r2]……[li,ri],如果已知了[l1,r1]的結果,那麼我們肯定很快能夠求出[l1+1,r1+1],[l1-1,r1+1],[l1-1,r1-1],[l1+1,r1-1]的結果。所以當我們去看第二個詢問的時候,我們就可以通過第一個詢問的結論,一步步地從[l1,r1]走向[l2,r2],此時我們所需要的時間就是O(|L2-L1|+|R2-R1|),這個有一個名字叫做曼哈頓距離。於是問題就變成了構造曼哈頓最小生成樹,然後按照生成樹的順序進行求解,使得速度大大提升。

但是求曼哈頓生成樹比較困難,然而還有一種分塊思想,能夠比較迅速而高效地解決這個問題。

我們把n個數分塊成為sqrt(n)塊,然後把詢問進行排序:如果兩個詢問的l是在同一塊裡面的,就按r從小到大排。如果不同塊,就按照塊從小到大排。在同一個塊裡面轉移的時候,由於每個塊裡面有n^0.5個元素,所以轉移最大時間複雜度就是O((n^0.5)*(n^0.5))=O(n)。如果在不同塊,最大複雜度是O(n^1.5)。所以最終時間複雜度為O(n^1.5)。

【題意】有n 個數,m個詢問。每次詢問在區間[l,r]裡面,有多少種情況使得ai^ai+1^……^aj=k。

               範圍:1 ≤ n, m ≤ 100 000, 0 ≤ k ≤ 1 000 000。

【解題思路】

對於這類區間上的問題,我們可以獲得陣列的異或字首和pre[i]=a1^a2......ai。

這樣對於區間[l,r],我們可以知道這個區間的異或值為pre[l-1]^pre[r]。

對於不需要修改的區間詢問問題,可以採用莫隊演算法。

所以對於這個問題,我們已經知道了字首和,所以可以知道:如果pre[i-1]^pre[j]=k,那麼就有pre[j]^k=pre[i-1]。這個時候我們開一個數組num,來記錄pre[i-1]^pre[j]=k時候的個數,我們轉移時就只需要對答案sum+=num[pre[i-1]^pre[j]]就可以了,再化簡一下就是,sum+=num[pre[j]^k]。

【AC程式碼】

Cf #340 div2 E
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1<<20;

struct node{
   int l,r,id;
}Q[maxn];
int n,m,k;
int pos[maxn];
long long ans[maxn];
long long flag[maxn];
int a[maxn];
bool cmp(node a,node b){
    if(pos[a.l]==pos[b.l])
        return a.r<b.r;
    return pos[a.l]<pos[b.l];
}
int L=1,R=0;
long long Ans=0;
void add(int x)
{
    Ans+=flag[a[x]^k];//這裡的a[x]已經表示了字首和
    flag[a[x]]++;
}
void del(int x)
{
    flag[a[x]]--;
    Ans-=flag[a[x]^k];
}
int main(){
    scanf("%d%d%d",&n,&m,&k);
    int sz=sqrt(n);
    for(int i=1; i<=n; i++){
        scanf("%d",&a[i]);
        a[i]^=a[i-1];
        pos[i]=i/sz;
    }
    for(int i=1; i<=m; i++){
        scanf("%d%d",&Q[i].l,&Q[i].r);
        Q[i].id=i;
    }
    sort(Q+1,Q+m+1,cmp);
    flag[0]=1;//代表每個前綴出現的次數
    for(int i=1; i<=m; i++){
        while(L<Q[i].l)
        {
            del(L-1);
            L++;
        }
        while(L>Q[i].l)
        {
            L--;
            add(L-1);
        }
        while(R<Q[i].r)
        {
            R++;
            add(R);
        }
        while(R>Q[i].r)
        {
            del(R);
            R--;
        }
        ans[Q[i].id]=Ans;
    }
    for(int i=1; i<=m; i++)
    {
        cout<<ans[i]<<endl;
    }
}

【補充】這是自己弄懂的第一個莫隊的題吧,真的有被這種方法驚豔到。orzzzzzzzzzzzz I love Lucky.