• 2019牛客第八场多校 E_Explorer 可撤销并查集(栈)+线段树


    @

    题意:

    链接

    题目类似:CF366D,Gym101652T
    本题给你(n(100000))个点(m(10000))条边,每无向边允许通过编号在([Li,Ri](1le Lile Rile 1e9))内的人,问从(1)(n)能通过多少个人。


    分析:

    赛中艹了30多发暴力无济于事。。。
    因为以前做过一道数据范围1000的原题,当时做法好像是离散化后枚举区间暴力跑(dfs)查询或者带着区间跑暴力(BFS)最后检查一遍。。。

    流下了菜鸡的泪水,主要是思维受限了,这很致命。
    听说这题可以LCT写,目前只会可撤销并查集+线段树的写法。。
    在这里插入图片描述

    • 上面讲的也很清楚了,把所有的权值离散化一下,然后建一个以权值为关键字的线段树
    • 这里提一下,这个线段树和上一场牛客的E题一模一样,是左闭右开线段树,就是每个叶子节点代表的是一段权值区间。这个线段树的解释:here
    • 然后线段树上每个节点存的是覆盖当前点的权值区间的所有边。
    • 区间更新完之后,把整颗树遍历一遍得出答案。
    • 对于从根到叶子节点这段的权值区间中包含的边,用一个并查集维护连通性。
    • 回溯时要撤销上面几条边的影响。记录一下合并上一条边的两个根,然后还原他们的(fa[],rnk[])就行。
    • 用一个栈记录加入的边即可,后进先出的结构正适合本题的撤销操作。还有,可撤销并查集不能路径压缩,必须按秩合并。

    Code

    const int MXN = 3e3 + 7;
    const int MXE = 2e6 + 7;
    int n, m;
    vector<int> sum[MXN<<2], vs;
    int fa[MXN], rnk[MXN], top;
    struct lh {
        int fi, se, u, v;
    }stak[MXN];
    struct lp {
        int u, v, l ,r;
    }cw[MXN];
    void update(int L, int R, int v, int l ,int r, int rt) {
        if(L <= l && r <= R) {
            sum[rt].eb(v);
            return;
        }
        int mid = (l + r) >> 1;
        if(L > mid) update(L, R, v, mid + 1, r, rt<<1|1);
        else if(R <= mid) update(L, R, v, l, mid, rt<<1);
        else {
            update(L, mid, v, l, mid, rt<<1), update(mid + 1, R, v, mid + 1, r, rt<<1|1);
        }
    }
    int Fi(int x) {
        return fa[x] == x? x: Fi(fa[x]);
    }
    int pa, pb, ans;
    void build(int l, int r, int rt) {
        if(l == r) {
    //        debug(l, r, Fi(3), Fi(5))
            for(auto x: sum[rt]) {
                pa = Fi(cw[x].u), pb = Fi(cw[x].v);
                if(pa == pb) continue;
    //            debug(l, r, cw[x].u, cw[x].v)
                if(rnk[pa] > rnk[pb]) swap(pa, pb);
                fa[pa] = pb;
                rnk[pb] += rnk[pa];
                stak[++ top] = {rt, x, pa, pb};
            }
            if(Fi(1) == Fi(n)) ans += vs[l+1] - vs[l];
            while(top && stak[top].fi == rt) {//撤销
                int x = stak[top].se;
                if(rnk[stak[top].u] > rnk[stak[top].v]) {
                    fa[stak[top].v] = stak[top].v;
                    rnk[stak[top].u] -= rnk[stak[top].v];
                }else {
                    fa[stak[top].u] = stak[top].u;
                    rnk[stak[top].v] -= rnk[stak[top].u];
                }
                -- top;
            }
    //        debug(l, r, Fi(3), Fi(5))
            return;
        }
        int mid = (l + r) >> 1;
    //    debug(l, r, mid, rt)
        for(auto x: sum[rt]) {
            pa = Fi(cw[x].u), pb = Fi(cw[x].v);
            if(pa == pb) continue;
    //        debug(pa, pb)
            if(rnk[pa] > rnk[pb]) swap(pa, pb);
            fa[pa] = pb;
            rnk[pb] += rnk[pa];
            stak[++ top] = {rt, x, pa, pb};
        }
        build(l, mid, rt << 1), build(mid + 1, r, rt << 1 | 1);
        while(top && stak[top].fi == rt) {//撤销
            int x = stak[top].se;
            if(rnk[stak[top].u] > rnk[stak[top].v]) {
                fa[stak[top].v] = stak[top].v;
                rnk[stak[top].u] -= rnk[stak[top].v];
            }else {
                fa[stak[top].u] = stak[top].u;
                rnk[stak[top].v] -= rnk[stak[top].u];
            }
            -- top;
        }
    }
    int main() {
    #ifndef ONLINE_JUDGE
        freopen("/home/cwolf9/CLionProjects/ccc/in.txt", "r", stdin);
    //    freopen("/home/cwolf9/CLionProjects/ccc/out.txt", "w", stdout);
    #endif
        n = read(), m = read();
        for(int i = 1; i <= n; ++i) fa[i] = i, rnk[i] = 1;
        vs.eb(0);
        for(int i = 1; i <= m; ++i) {
            cw[i].u =read(), cw[i].v = read();cw[i].l = read(), cw[i].r = read();
            vs.eb(cw[i].l), vs.eb(cw[i].r + 1);
        }
        my_unique(vs);
    //    for(auto x: vs) printf("%d ", x); printf("
    ");
        for(int i = 1; i <= m; ++i) {
            update(lower_bound(all(vs), cw[i].l) - vs.begin(), upper_bound(all(vs), cw[i].r) - vs.begin() - 1, i, 1, vs.size() - 1, 1);
    //        debug(lower_bound(all(vs), cw[i].l) - vs.begin(), upper_bound(all(vs), cw[i].r) - vs.begin() - 1, cw[i].u, cw[i].v);
        }
        build(1, vs.size() - 1, 1);
        printf("%d
    ", ans);
        return 0;
    }
    

    两道待补练习题:CF 813F 可撤销并查集+分治BZOJ 3237 CDQ分治+带撤销并查集
    复习带权并查集和可持久化并查集。

  • 相关阅读:
    luogu P1840 Color the Axis_NOI导刊2011提高(05)|并查集
    luogu P5414 [YNOI2019]排序 |动态规划
    luogu P4064 [JXOI2017]加法 |二分+堆
    luogu P4065 [JXOI2017]颜色 |随机化+前缀和
    luogu P2135 方块消除 |dp
    luogu P1650 田忌赛马 |贪心
    IM群聊消息究竟是存1份(即扩散读)还是存多份(即扩散写)?
    IM群聊消息的已读回执功能该怎么实现?
    IPv6技术详解:基本概念、应用现状、技术实践(下篇)
    IPv6技术详解:基本概念、应用现状、技术实践(上篇)
  • 原文地址:https://www.cnblogs.com/Cwolf9/p/11334108.html
Copyright © 2020-2023  润新知