2023.9.25记录

做了做并查集

[JSOI2008] 星球大战

JSOI2008] 星球大战 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题意

给定一个无向图,每次操作删除一个点,求每次操作后连通块的数量。

思路

可以用并查集做。按操作顺序不好计算连通块的数量,所以可以考虑按操作的逆向顺序计算。

因为每两个连通块相连会使连通块的数量减一,所以在计算出所有节点被删除后的连通块数量后,再按反向顺序往并查集里加入节点,此时新加入的节点就是一个连通块,所以连通块数量加一。然后检查该节点与值相邻的节点,如果相邻节点已经在并查集中且与该节点不在一个连通块里,则连通块的数量减一。

代码

#include<bits/stdc++.h>

using namespace std;
using i64 = long long;

class DSU {
private:
    vector<int> p, siz;
    
public:
    DSU(int n) {
        init(n);
    }

    void init(int n) {
        p.resize(n);
        iota(p.begin(), p.end(), 0);
        siz.assign(n, 1);
    }
     
    int find(int x) {
        int t = x;
        while(x != p[x]) {
            x = p[x] = p[p[x]];
        }
        return p[t] = x;
    }
    
    bool merge(int u, int v) {
        int x = find(u);
        int y = find(v);
        if(x == y) {
            return false;
        }
        siz[x] += siz[y];
        p[y] = x;
        return true;
    }
    
    bool same(int x, int y) {
        return find(x) == find(y);
    }
    
    int size(int x) {
        return siz[find(x)];
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n, m;
    cin >> n >> m;

    vector<vector<int>> adj(n);
    for(int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }

    int k;
    cin >> k;

    vector<int> isBroken(n), broken(k); 
    for(int i = 0; i < k; i++) {
        cin >> broken[i];
        isBroken[broken[i]] = true;
    }

    DSU D(n);
    int res = n - k;
    for(int u = 0; u < n; u++) {
        for(int v : adj[u]) {
            if(isBroken[u] || isBroken[v]) {
                continue;
            }
            if(D.merge(u, v)) {
                res -= 1;
            }
        }
    }

    vector<int> ans(k + 1);
    ans[k] = res;
    for(int i = k - 1; i >= 0; i--) {
        int u = broken[i], cnt = 0;
        for(int v : adj[u]) {
            if(isBroken[v]) {
                continue;
            } 
            if(D.merge(u, v)) {
                cnt += 1;
            }
        }
        isBroken[u] = false;
        res -= cnt - 1;
        ans[i] = res;
    }

    for(int x : ans) {
        cout << x << "\n";
    }

    return 0;
}

[NOIP2010 提高组] 关押罪犯

NOIP2010 提高组] 关押罪犯 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题意

给定一个带权无向图,分为两个连通块,使得这两个连通块中最大的边权最小。

思路

把所有边按权值从大到小排序,然后从大到小判断。

如果这条边的两个端点已在同一个集合中,则这条边的边权就是答案。否则再按端点判断,如果其中一个端点的另一条边已经出现过,就把另一个端点与另一条边另一个端点合并。

代码

#include<bits/stdc++.h>

using namespace std;
using i64 = long long;

class DSU {
private:
    vector<int> p, siz;
    
public:
    DSU(int n) {
        init(n);
    }

    void init(int n) {
        p.resize(n);
        iota(p.begin(), p.end(), 0);
        siz.assign(n, 1);
    }
     
    int find(int x) {
        int t = x;
        while(x != p[x]) {
            x = p[x] = p[p[x]];
        }
        return p[t] = x;
    }
    
    bool merge(int u, int v) {
        int x = find(u);
        int y = find(v);
        if(x == y) {
            return false;
        }
        siz[x] += siz[y];
        p[y] = x;
        return true;
    }
    
    bool same(int x, int y) {
        return find(x) == find(y);
    }
    
    int size(int x) {
        return siz[find(x)];
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n, m;
    cin >> n >> m;

    vector<array<int, 3>> v(m);
    for(int i = 0; i < m; i++) {
        cin >> v[i][0] >> v[i][1] >> v[i][2];
    }

    sort(v.begin(), v.end(), [&](array<int, 3> &a, array<int, 3> &b) {
        return a[2] > b[2];
    });

    DSU D(n + 1);
    vector<int> e(n + 1);
    for(auto [u, v, w] : v) {
        if(D.same(u, v)) {
            cout << w << "\n";
            return 0;
        }
        e[u] ? D.merge(e[u], v) : e[u] = v;
        e[v] ? D.merge(e[v], u) : e[v] = u;
    }

    cout << 0 << "\n";

    return 0;
}
posted @ 2023-09-25 22:17  wuyoudexian  阅读(31)  评论(0)    收藏  举报