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;  
    }  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值