并查集(洛谷试炼场——提高历练地)

并查集

普通并查集

  • 查找+路径压缩

    int find(int x){
        if(pre[x] == x) return x;
        return pre[x] = find(pre[x]);
    }
    
  • 合并

    void Union(int x, int y){
    	int fx = find(x), fy = find(y);
        if(fx != fy) pre[fx] = fy;
    }
    

例题:P3367【模板】并查集

  • 思路:

    #include
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    #define ll long long
    #define db double
    #define PII pair<int, int>
    #define pi acos(-1.0)
    const int INF = 0x7fffffff;
    const int N = 1e5 + 5;
    const db eps = 1e-10;
    using namespace std;
    int n, m, pre[N];
    int find(int x){
        if(pre[x] == x) return x;
        return pre[x] = find(pre[x]);
    }
    void join(int x,int y){
        int fx = find(x), fy = find(y); 
        if(fx != fy) pre[fx] = fy;
    }
    int main(){
        cin >> n >> m;
        rep(i, 1, n) pre[i] = i;
        rep(i, 1, m){
            int op, x, y; scanf("%d%d%d", &op, &x, &y);
            if(op == 1) join(x, y);
            else puts(find(x) == find(y) ? "Y" : "N");
        }
    }
    

例题:P1111 修复公路

  • 思路:
    #include
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    #define ll long long
    #define db double
    #define PII pair<int, int>
    #define pi acos(-1.0)
    const int INF = 0x7fffffff;
    const int N = 1e3 + 5, M = 1e5 + 5;
    const db eps = 1e-10;
    using namespace std;
    int m, n, pre[N], now = 0, cnt = 0;
    struct AC{
        int x, y, t;
    }a[M];
    bool cmp(AC a, AC b){
        return a.t < b.t;
    }
    int find(int x){
        if(pre[x] == x) return x;
        return pre[x] = find(pre[x]);
    }
    void join(int x, int y){
        int fx = find(x), fy = find(y);
        if(fy != fx) pre[fx] = fy;
    }
    int main(){
        cin >> n >> m;
        rep(i, 1, m) scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].t);
        sort(a + 1, a + 1 + m, cmp);
        rep(i, 1, n) pre[i] = i;
        rep(i, 1, m){
            int nx = a[i].x, ny = a[i].y;
            if(find(nx) != find(ny)){  //只统计可连接不同集合内的
                join(nx, ny);
                now = a[i].t;
                cnt++;
            }
            if(cnt == n - 1){  //n个节点只需要 n - 1 条连接两个不同集合内的边即可全部连接
                cout << now << endl;
                return 0;
            }
        }
        if(cnt < n - 1) cout << -1 << endl;
    }
    

例题:P1197星球大战

  • 思路:
    //并查集 && 逆推思想
    #include
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    #define ll long long
    #define db double
    #define PII pair<int, int>
    #define pi acos(-1.0)
    const int INF = 0x7fffffff;
    const int N = 4e5 + 5;
    const db eps = 1e-10;
    using namespace std;
    int m, n, k, a[N], pre[N], ans[N];
    bool vis[N];
    vector<int> g[N];
    int find(int x){
        if(pre[x] == x) return x;
        return pre[x] = find(pre[x]);
    }
    int main(){
        cin >> n >> m;
        rep(i, 1, m){
            int x, y; scanf("%d%d", &x, &y);
            g[x].push_back(y), g[y].push_back(x);
        }
        cin >> k;
        rep(i, 0, n - 1) vis[i] = 1, pre[i] = i;
        rep(i, 1, k){
            scanf("%d", &a[i]);
            vis[a[i]] = 0;
        }
        ans[k + 1] = n - k;
        rep(i, 0, n - 1){  //初始化
            if(!vis[i]) continue;
            for(auto nxt : g[i]){
                if(!vis[nxt]) continue;
                int fx = find(nxt), fy = find(i);  //Union
                if(fx != fy){
                    pre[fx] = fy;
                    ans[k + 1]--;
                }
            }
        }
        per(i, k, 1){  //逆推
            int u = a[i];
            vis[u] = 1, ans[i] = ans[i + 1] + 1;  //加入一个a[i]的连通块
            for(auto nxt : g[u]){
                if(!vis[nxt]) continue;
                int fx = find(nxt), fy = find(u);
                if(fx != fy){
                    pre[fx] = fy;
                    ans[i]--;
                }
            }
        }
        rep(i, 1, k + 1) cout << ans[i] << endl;
    }
    

带权并查集

v a l [ i ] val[i] val[i]记录节点 i i i与父节点的关系

  • 查找+路径压缩

    int find(int x){
    	if(x == pre[x]) return x;
        int root = find(pre[x]);  //一直找到根节点
        ...//压缩时要更新权值,此时val[pre[x]]在上一行已更新完
        return pre[x] = root;
    }
    
  • 合并

    void Union(int x, int y){
        int fx = find(x), fy = find(y);
        if(fx != fy){
            pre[fx] = fy;
            ...//更新权值
        }
    }
    

例题:P1196银河英雄传说

  • 思路:
    #include
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    #define ll long long
    #define db double
    #define PII pair<int, int>
    #define pi acos(-1.0)
    const int INF = 0x7fffffff;
    const int N = 3e4 + 5;
    const db eps = 1e-10;
    using namespace std;
    int t, n, a[N], pre[N], val[N], sz[N], x, y;  //sz数组记录所在列的大小, val[i]数组记录 i 到本列根节点的距离
    char op;
    int find(int x){
    	if(x == pre[x]) return x;
        int fx = find(pre[x]);
        val[x] = val[pre[x]] + val[x];  //val[x] 表示 x->pre[x], val[fx] 表示 pre[x]->fx. 上一行函数已更新 val[pre[x]]
        sz[x] = sz[fx];
        return pre[x] = fx;
    }
    void Union(int x, int y){
        int fx = find(x), fy = find(y);
        pre[fx] = fy;
        val[fx] += sz[fy];
        sz[fy] = sz[fx] = sz[fx] + sz[fy];
    }
    int main(){
        cin >> t;
        rep(i, 1, N) pre[i] = i, val[i] = 0, sz[i] = 1;
        rep(i, 1, t){
            cin >> op >> x >> y;
            if(op == 'M') Union(x, y);
            else{
                if(find(x) == find(y)) cout << abs(val[x] - val[y]) - 1 << endl;
                else cout << -1 << endl;
            }
            rep(i, 1, 4) cout << i << " " << pre[i] << " " << val[i] << endl;
        }
    }
    

种类并查集

种类并查集一般可以用扩展域并查集,也就是增加维度,每个元素的每个维度都是一个节点,判断两个节点是否在同一个集合

例题:P2024食物链

  • 思路:

    //扩展域并查集
    #include
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    #define ll long long
    const int INF = 0x7fffffff;
    const int N = 3e5 + 5;
    using namespace std;
    int k, n, pre[N], op[N], x[N], y[N], res = 0;
    int find(int x){
        if(pre[x] == x) return x;
        return pre[x] = find(pre[x]);
    }
    void Union(int x, int y){
        int fx = find(x), fy = find(y);
        if(fx != fy) pre[fx] = fy;
    }
    int main(){
        ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
        cin >> n >> k;
        rep(i, 1, 3 * n) pre[i] = i;
        rep(i, 1, k){
            cin >> op[i] >> x[i] >> y[i];
            if(x[i] > n || y[i] > n || (op[i] == 2 && x[i] == y[i])){
                res++, cout << i << endl;
                continue;
            }
            int xself = x[i], xenemy = x[i] + n, xeat = x[i] + n + n;
            int yself = y[i], yenemy = y[i] + n, yeat = y[i] + n + n;
            if(op[i] == 1){
                if(find(xself) == find(yeat) || find(xself) == find(yenemy)) res++, cout << i << endl;
                else Union(xself, yself), Union(xenemy, yenemy), Union(xeat, yeat); 
            }
            else{
                if(find(xself) == find(yself) || find(xself) == find(yeat)) res++, cout << i << endl;
                else Union(xself, yenemy), Union(xenemy, yeat), Union(xeat, yself); 
            }
        }
        cout << res << endl;
    }
    

    当然用带权并查集也能做

    //带权并查集
    #include
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    #define ll long long
    const int INF = 0x7fffffff;
    const int N = 1e5 + 5;
    using namespace std;
    int k, n, pre[N], op[N], x[N], y[N], res = 0, val[N];
    int find(int x){
        if(pre[x] == x) return x;
        int root = find(pre[x]);
        (val[x] += val[pre[x]]) %= 3;  //更新权值
        return pre[x] = root;
    }
    int main(){
        ios::sync_with_stdio(0), cin.tie(0);
        cin >> n >> k;
        rep(i, 1, n) pre[i] = i, val[i] = 0;
        rep(i, 1, k){
            cin >> op[i] >> x[i] >> y[i];
            if(x[i] > n || y[i] > n || (op[i] == 2 && x[i] == y[i])){
                res++;
                continue;
            }
            int fx = find(x[i]), fy = find(y[i]);
            if(fx == fy){
                if(op[i] == 1 && val[x[i]] != val[y[i]]) res++;
                if(op[i] == 2 && val[x[i]] != (val[y[i]] + 1) % 3) res++;
            }
            else{
                pre[fx] = fy;
                val[fx] = (val[y[i]] + (op[i] == 1 ? 0 : 1) - val[x[i]] + 3) % 3;
            }
        }
        cout << res << endl;
    }
    
    //带权并查集
    #include
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    #define ll long long
    const int INF = 0x7fffffff;
    const int N = 1e5 + 5;
    using namespace std;
    int k, n, pre[N], op[N], x[N], y[N], res = 0, val[N];
    int find(int x){
        if(pre[x] == x) return x;
        int root = find(pre[x]);
        (val[x] += val[pre[x]]) %= 3;  //更新权值
        return pre[x] = root;
    }
    void Union(int x, int y, int op){
        int fx = find(x), fy = find(y);
        if(fx != fy){
            pre[fx] = fy;
            val[fx] = (val[y] + (op == 1 ? 0 : 1) - val[x] + 3) % 3;
        }
    }
    int main(){
        ios::sync_with_stdio(0), cin.tie(0);
        cin >> n >> k;
        rep(i, 1, n) pre[i] = i, val[i] = 0;
        rep(i, 1, k){
            cin >> op[i] >> x[i] >> y[i];
            if(x[i] > n || y[i] > n || (op[i] == 2 && x[i] == y[i])){
                res++;
                continue;
            }
            if(op[i] == 1){
                if(find(x[i]) == find(y[i]) && val[x[i]] != val[y[i]]) res++;
                else Union(x[i], y[i], op[i]);
            }
            else{
                if(find(x[i]) == find(y[i]) && val[x[i]] != (val[y[i]] + 1) % 3) res++;
                else Union(x[i], y[i], op[i]);
            }
        }
        cout << res << endl;
    }
    

你可能感兴趣的:(专题总结,洛谷试炼场,算法)