1 条题解

  • 0
    @ 2024-7-31 15:43:14

    题解

    思路分析

    【方法1】二分答案+染色二分图

    题目中有明显的求最大值最小的概念,于是可以考虑二分答案,对于最大值检查可以采用二分图染色。

    【方法2】贪心+并查集(带权/扩展域名)

    由于题目的答案一定是其中的某一条边长,怎么使得该边长最小? 可以想到每次先将较大值放入不同监狱,如果可以放置,那么后面的答案一定更小;如果不能放置,则其就是答案。

    所以可以按照边长排序,降序合并每条边即可。

    合并的时候有两种策略

    1. 带权并查集:使用权值来维护当前元素与根节点元素的种类关系,如 d[u]=0表示 u与root是同类,d[u]=1表示不同类。带权并查集的find和union操作需要适当更新权值数组d[u];
    int find(int u) {
        if (u != p[u]) {
            int fu = p[u];
            p[u] = find(p[u]);
            d[u] = (d[u] + d[fu]) % 2;
        }
        return p[u];
    }
    void union_(int u, int v) {
        int a = find(u), b = find(v);
        p[a] = b;
        d[a] = ((d[v] - d[u] + 2) % 2 + 1) % 2;
    }
    
    1. 扩展域并查集:将并查集的规模扩展为原来的两倍,前一半用于维护朋友关系,后一半用于维护敌人关系。如 (u,u+n) 是敌人,(v,v+n) 是敌人,当 (u,v)是敌人的时候,根据敌人的敌人是朋友,可以将 u 与 u的敌人(v)的敌人(v+n)合并,即合并 (u, v+n);同理合并 (v, u+n)。

    程序实现

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N = 2e4 + 10, M = 1e5 + 10, INF = 0x3f3f3f3f;
    int n, m, p[N << 1], d[N << 1];
    struct T {
        int u, v, w;
        bool operator<(const T& t) const { return w < t.w; }
    } g[M];
    
    // -------------------
    // 二分答案+染色二分图
    vector<int> G[N];
    int color[N], flag;
    void dfs(int u, int c) {
        color[u] = c;
        for (auto v : G[u]) {
            if (!color[v]) dfs(v, ~c);
            else if (color[v] == c) flag = 0;
        }
    }
    bool chk(int k) {  // 将边权 >k 的建图,检查是否可以进行二分图染色
        for (int i = 1; i <= n; i++) G[i].clear();
        for (int i = k + 1; i <= m; i++) {
            int u = g[i].u, v = g[i].v;
            G[u].push_back(v), G[v].push_back(u);
        }
        flag = 1, memset(color, 0x00, sizeof color);
        for (int i = 1; i <= n && flag; i++)
            if (!color[i]) dfs(i, 1);
        return flag;
    }
    int solve1() {
        int l = 0, r = m, ans = 0;
        while (l <= r) {
            int mid = l + r >> 1;
            chk(mid) ? r = mid - 1, ans = mid : l = mid + 1;
        }
        return g[ans].w;
    }
    // -------------------
    int find(int u) {
        if (u != p[u]) {
            int fu = p[u];
            p[u] = find(p[u]);
            d[u] = (d[u] + d[fu]) % 2;
        }
        return p[u];
    }
    // 带权并查集
    // d[i]表示 i与根节点的关系:0-朋友,1-敌人
    int solve2() {
        for (int i = 0; i <= n; i++)
            p[i] = i, d[i] = 0;
        for (int i = m; i >= 1; i--) {
            int u = g[i].u, v = g[i].v;
            int a = find(u), b = find(v);
            if (a == b && (d[u] - d[v] + 4) % 2 == 0)
                return g[i].w;
            p[a] = b;
            d[a] = ((d[v] - d[u] + 2) % 2 + 1) % 2;
        }
        return 0;
    }
    // 扩展域并查集
    // 敌人的敌人是朋友:u,u+n是敌人,u,v是敌人,将u放入v的敌人v+n的集合
    int solve3() {
        for (int i = 0; i <= n * 2; i++)
            p[i] = i;
        for (int i = m; i >= 1; i--) {
            int u = g[i].u, v = g[i].v;
            int a = find(u), b = find(v);
            if (a == b)
                return g[i].w;
            p[a] = find(v + n);
            p[b] = find(u + n);
        }
        return 0;
    }
    int main() {
        cin >> n >> m;
        for (int i = 1, u, v, w; i <= m; i++)
            cin >> u >> v >> w, g[i] = {u, v, w};
        sort(g + 1, g + 1 + m);
    
        // cout << solve1() << endl;//AC
        // cout << solve2() << endl;//AC
        cout << solve3() << endl;  // AC
        return 0;
    }
    
    • 1

    信息

    ID
    344
    时间
    1000ms
    内存
    125MiB
    难度
    10
    标签
    递交数
    9
    已通过
    1
    上传者