Chgtaxihe's blog Chgtaxihe's blog
首页
练习
算法学习
读书笔记
小玩意

Chgtaxihe

首页
练习
算法学习
读书笔记
小玩意
  • 数论

  • 动态规划

  • 暴力

  • 图论

    • 2-SAT学习笔记
    • 最短路学习笔记
    • KM学习笔记
    • 一般图最大匹配学习笔记
    • 二分图匹配学习笔记
    • 可撤销并查集学习笔记
      • 按秩合并
      • 例题 Codeforces 892E
    • 多源最短路学习笔记
    • 斯坦纳树学习笔记
    • 最小异或生成树学习笔记
    • LCA学习笔记
    • 树的直径与重心
    • 树的重心
    • 矩阵树定理学习笔记
    • 网络流-最小割学习笔记
    • 网络流-最小费用最大流学习笔记
  • 数据结构

  • 字符串

  • 几何

  • 博弈论

  • 其他

可撤销并查集学习笔记

# 可撤销并查集

大前提:任意时刻任意两个点之间至多只有一条路径

一般的并查集(路径压缩优化)是无法回退的,当我们想要复用并查集时,有3种思路

  1. 可持久化数组(可持久化线段树)维护并查集
  2. 按秩合并并查集(用堆来维护历史信息)
  3. Link-Cut Tree(最实用)

本文记录的是第二种方法

# 按秩合并

核心代码

struct Union_Find_Set{ // 并查集
    static const int max_node = 5e5 + 100;
    int ufs[max_node], depth[max_node];

    vector<pair<int, pair<int, int> > > stk;

    void init(int n){
        for(int i=0; i<=n; i++) {
            ufs[i] = i;
            depth[i] = 1;
        }
    }

    int find(int x){
        return ufs[x]==x?x:find(ufs[x]);
    }
	// remember 表示该条记录是否可能要被回退
    bool merge(int u, int v, bool remember){
        u = find(u), v = find(v);
        if(u == v) return false;
        if(depth[u] < depth[v]) swap(u, v);
        if(remember) stk.push_back({v, {u, depth[u]}});
        depth[u] = max(depth[u], depth[v] + 1);
        ufs[v] = u; // 让深度小的接在深度大的下面
        return true;
    }

    void recover(){ // 回退
        if(stk.empty()) return;
        pair<int, pair<int, int> > rec = stk.back();
        stk.pop_back();
        ufs[rec.first] = rec.first;
        depth[rec.second.first] = rec.second.second;
    }

}ufs;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 例题 Codeforces 892E

题意:给一个联通的无向图GGG,qqq次询问:给定kkk条边,问这些边是否可能同时出现在一颗最小生成树中。

由Kruskal算法可知,如果当前边ei(wi=x)e_i(w_i=x)ei​(wi​=x)在最小生成树中,那么在加入所有边{ej∣wj<x}\{e_j\mid w_j<x\}{ej​∣wj​<x}后,eie_iei​的两端点属于不同的连通分量。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 5e5 + 1000;

struct edge{
    int u, v, w, id;
    bool operator <(const edge & e) const{
        return w < e.w;
    }
}es[maxn];

int id2e[maxn];
bool ans[maxn];
vector<pair<int, int> > querys[maxn];

struct Union_Find_Set{
    // 略
}ufs;

void handle(int x, int l, int r){
    int cnt = 0;
    vector<pair<int, int> > & vec = querys[x];
    for(int i=l; i<=r; i++){
        int e = vec[i].second;
        if(!ufs.merge(es[e].u, es[e].v, true)){ // 需要回退的边
            ans[vec[i].first] = 1;
            break;
        }
        cnt++;
    }
    for(int i=0; i<cnt; i++) ufs.recover(); // 回退cnt步
}

void solve(){
    int n, m, q;
    cin >> n >> m;
    ufs.init(n + 1);

    for(int i=1; i<=m; i++){
        es[i].id = i;
        cin >> es[i].u >> es[i].v >> es[i].w;
    }
    sort(es + 1, es + m + 1);
    for(int j=1; j<=m; j++) id2e[es[j].id] = j;
	// 离线做法
    cin >> q;
    for(int i=0, k; i<q; i++){
        cin >> k;
        for(int j=0, x; j<k; j++){
            cin >> x;
            querys[es[id2e[x]].w].push_back({i, id2e[x]});
        }
    }

    for(int l=1, r=1; l<=m; l=r){
        int w = es[l].w;
        // 找到所有权值等于w的边
        while(r <= m && es[r].w == w) r++;
        for(int ql=0, qr=0; ql < (int)querys[w].size(); ql = qr){
            // 找到w相同且在同一次询问的中边
            while(qr < (int)querys[w].size() && querys[w][ql].first == querys[w][qr].first) qr++;
            handle(w, ql, qr - 1);
        }

        for(int i=l; i<r; i++) ufs.merge(es[i].u, es[i].v, false); // 无需回退的边
    }

    for(int i=0; i<q; i++) {
        cout << (ans[i]?"NO\n":"YES\n");
    }
}

signed main(){
    solve();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
上次更新: 2021/02/24, 03:37:30
二分图匹配学习笔记
多源最短路学习笔记

← 二分图匹配学习笔记 多源最短路学习笔记→

最近更新
01
深入理解Java虚拟机
03-04
02
DNS工作原理
02-27
03
改用VuePress啦
02-23
更多文章>
Theme by Vdoing | Copyright © 2019-2021
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式