2019牛客第八場多校 E_Explorer 可撤銷並查集(棧)+線段樹

@(2019牛客暑期多校訓練營(第八場)E_Explorer)

題意:

鏈接

題目類似:CF366D,Gym101652T
本題給你\(n(100000)\)個點\(m(10000)\)條邊,每無向邊允許通過編號在\([Li,Ri](1\le Li\le Ri\le 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("\n");
    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\n", ans);
    return 0;
}

兩道待補練習題:CF 813F 可撤銷並查集+分治BZOJ 3237 CDQ分治+帶撤銷並查集
複習帶權並查集和可持久化並查集。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章