hdu 4677 並查集+分塊算法 好題 (2013多校聯合)

參考;http://blog.csdn.net/auto_ac/article/details/10672865

題意:點數n(n <= 30000), 邊數(m <= 90000)的圖,詢問 q(1<=q<=30000)。

對於每個詢問(l, r),去掉(l,r)區間以外的所有點和其相關聯的邊,問剩下來的圖的聯通塊的個數。
思路:分塊+並查集

分塊算法入門:http://blog.csdn.net/auto_ac/article/details/10050589

這題很容易想到分塊, 難點是並查集的處理。

對詢問離線分塊排序以後,我們對 左端點在相同塊號內的詢問 一起處理。這些詢問的右斷點是遞增的,

左端點在某個塊內,我們對每個詢問分成2個區域分別進行並查集處理,區域1:左端點所在的塊的區域(並查集tp)

(點的個數sqrt(n))。區域2:除區域1以外的其它詢問點(並查集f)。由於右斷點是遞增的,區域2很容易用一個並查集處理。區域1對於每個詢問,都重新處理一遍並查集。最後關鍵就是兩個並查集的合併,由於並查集不能進行刪除操作,我們需要做的是 利用並查集f裏面的信息而不破壞它,並且複雜度要符合題意。我們可以在維護並查集tp的同時讓這兩個並查集合並,即在並查集f中把我當前需要的點搬到並查集tp裏面,而沒有必要把並查集f全部搬過來,而且這樣做複雜度也不符合要求,用一個vis數組就可以做到

在實際處理的時候 並查集tp能處理到的點是整個詢問的區域內的點。

具體看代碼吧。

要清楚爲什麼要tp用的是f的。。



    #include <cstdio>  
    #include <cstring>  
    #include <cmath>  
    #include <algorithm>  
    #include <vector>  
    using namespace std;  
    #define pb push_back  
    const int maxn = 30004;  
    vector<int> edge[maxn];  
    int n, m;  
    int bsize;  
    struct node {  
        int l, r, b, no;//b塊號,no詢問編號  
        inline void in(int i) {  
            scanf("%d%d", &l, &r);  
            b = l/ bsize;  
            no = i;  
        }  
        bool operator <(const node &t) const { //先按左端點所在的塊號排,再按右端點排  
            if (b == t.b)  
                return r < t.r;  
            return b < t.b;  
        }  
    } q[maxn];  
    int cnt, sum;   //cnt: 右邊並查集f的聯通塊個數, sum 左邊並查集tp的聯通塊個數  
    int f[maxn], tp[maxn];  
    int vis[maxn];      //vis[x]表示最後一次用到x的詢問是哪一個,可以記錄當前詢問用沒有把點x放過   處理左邊的並查集tp中  
    //R: 處理區域2, 即右邊區域的並查集  
    int Rfind(int x) {  
        return x == f[x] ? x : f[x] = Rfind(f[x]);  
    }  
    void Rmerge(int x, int y) {  
        int rx = Rfind(x), ry = Rfind(y);  
        if (rx == ry) return;  
        f[rx] = ry;  
        cnt--;  
    }  

    //L: 處理區域1, 即左邊區域的並查集  
    int cur;//當前的詢問號  
    int Lfind(int x) {  
        if (vis[x] != cur) {//判斷點x是否加入    處理左邊的並查集tp, 沒加入就加進來,更新vis  
            vis[x] = cur;  
            tp[x] = f[x];  
        }  
        return x == tp[x] ? x : tp[x] = Lfind(tp[x]) ;  
    }  
    void Lmerge(int x, int y) {  
        int rx = Lfind(x);  
        int ry = Lfind(y);  
        if (rx == ry) return;  
        tp[rx] = ry;  
        sum--;  
    }  
    int ans[maxn];  
    int R, B;   //在當前詢問之前的詢問的右端點和塊號  

    int work(int l, int r, int b) {  
        int z = bsize * (b+1)-1, i, j;  //z是左右邊區間的分界點,z點屬於左邊  
        if (B != b) {           //新的一個塊的第一個詢問  
            R = z;  
            B = b;  
            cnt = 0;  
            for (i = z-bsize+1; i <= n; i++) //只要對f初始化,tp不用初始化(因爲用vis更新的緣故)。  
                f[i] = i;  
        }  
        //更新右邊並查集  
        for (i = max(R, z)+1; i <= r; i++) {  
            cnt++;  
            for (j = 0; j <(int) edge[i].size(); j++) {  
                int v = edge[i][j];  
                if (v > r || v <= z) continue;  
                Rmerge(i, v);  
            }  
        }  
        //更新左邊並查集  
        sum = 0;  
        for (i = l; i <= min(r, z); i++) {  
            sum++;  
            for (j = 0; j <(int) edge[i].size(); j++) {  
                int v = edge[i][j];  
                if (v > r || v < l) continue;  
                Lmerge(i, v);  
            }  
        }  

        R = r;  
        return sum+cnt;  
    }  
    void solve() {  
        int i, j, k;  
        R = B = -1;  
        for (i = 0; i < m; i++) {  
            j = i;  
            while (j < m && q[j].b == q[i].b) j++;   //對於左端點在一個塊內的詢問一起處理  
            for (k = i; k < j; k++) {  
                cur = k;  
                ans[q[k].no] = work(q[k].l, q[k].r, q[k].b);  
            }  
            i = j - 1;  
        }  
    }  
    int main() {  
        int i, cas, ca = 1;  
        scanf("%d", &cas);  
        while (cas--) {  
            scanf("%d%d", &n, &m);  
            for(i = 1; i <= n; i++)  
                edge[i].clear();  
            while (m--) {  
                int x, y;  
                scanf("%d%d", &x, &y);  
                edge[x].pb(y);  
                edge[y].pb(x);  
            }  
            bsize = sqrt(n + 0.5);  
            scanf("%d", &m);  
            for (i = 0; i < m; i++) q[i].in(i);  
            sort(q, q + m);  
            memset(vis, -1, sizeof(vis));  
            solve();  
            printf("Case #%d:\n", ca++);  
            for(i = 0; i < m; i++)  
                printf("%d\n", ans[i]);  
        }  
        return 0;  
    }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章