洛谷 P1525 关押罪犯(并查集 or 二分图)

题意

给你 N ( N ≤ 2 × 1 0 4 ) N(N \leq 2\times10^4) N(N2×104)个节点和 M ≤ 1 0 5 M \leq 10^5 M105对冲突关系,当这些关系中的点被分配到同一个集合的时候会产生大小为 c c c的冲突。求把这些点分成两个集合最小的最大冲突值。

解题思路

看到最小的最大值我又想二分了,这题确实可以二分,首先枚举最小的最大冲突值 m i d mid mid,然后把所有大于等于这个 m i d mid mid的冲突关系都拿来判定是否构成二分图,如果能就说明可以安全的把这些点放在不同的集合中,缩小枚举范围。这题之所以可以二分,是因为最小的最大冲突值满足单调性,也就是如果有 k k k使得所有大于等于 k k k的冲突关系都可以构成二分图,那么大于 k k k的冲突关系也能构成二分图。

但是这题有更加巧妙的解法,那就是用并查集。这个解法十分优雅,基本的原理就是先从大到小给这些冲突关系排序,然后对于每个关系,我们都尽量避免他们被分配到一个集合。也就是说假设you冲突关系 ( u , v ) (u, v) (u,v),那么尽量让 u u u属于集合1, v v v属于集合2,如果实在分配不了了就输出这个冲突值,因为我们对冲突关系排序了,所以保证这个值一定是最小的最大冲突值。

如何用并查集实现两个点属于不同的集合呢,每次合并关系的时候我们可以往并查集里放入 ( u → 1 ) (u \rightarrow 1) (u1),和 ( v → 0 ) (v \rightarrow 0) (v0),或者 ( u → 0 ) (u \rightarrow 0) (u0),和 ( v → 1 ) (v \rightarrow 1) (v1)。只要发现 ( u → 1 ) (u \rightarrow 1) (u1) ( v → 1 ) (v \rightarrow 1) (v1)或者 ( u → 0 ) (u \rightarrow 0) (u0) ( v → 0 ) (v \rightarrow 0) (v0)被分配到了一个同一个集合那么就说明冲突避免不了了。表示属于不同集合的方法也很简单,因为 N N N是有限的,那么我们可以让 u + N u + N u+N作为另一个集合的虚拟节点,具体代码如下。

时间复杂度

O ( M log ⁡ M ) O(M\log M) O(MlogM)

代码

#include <algorithm>
#include <bitset>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <list>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <vector>
using namespace std;
typedef long long ll;

const int INF = 2147483647;
const int INF2 = 0x3f3f3f3f;
const ll INF64 = 1e18;
const double INFD = 1e30;
const double EPS = 1e-6;
const double PI = 3.1415926;
const ll MOD = 1e9;
// 读入优化
inline int read() {
    int X = 0, w = 0;
    char ch = 0;
    while (!isdigit(ch)) {
        w |= ch == '-';
        ch = getchar();
    }
    while (isdigit(ch)) X = (X << 3) + (X << 1) + (ch ^ 48), ch = getchar();
    return w ? -X : X;
}

struct Edge {
    int fr;
    int to;
    int dis;
    Edge() = default;
    Edge(int fr, int to, int dis) : fr(fr), to(to), dis(dis) {}
    bool operator<(const Edge& b) const { return this->dis > b.dis; }
};

int n, m, k;
int CASE;
const int MAXN = 20005 * 2;
vector<Edge> edges;
int parent[MAXN];

inline int find(int p) {
    while (p != parent[p]) {
        parent[p] = parent[parent[p]];
        p = parent[p];
    }
    return p;
}

void unite(int x, int y) { parent[find(x)] = find(y); }

int main() {
#ifdef LOCALLL
    freopen("in", "r", stdin);
    freopen("out", "w", stdout);
#endif
    scanf("%d %d", &n, &m);
    for (int i = 0; i < m; i++) {
        int u, v, w;
        scanf("%d %d %d", &u, &v, &w);
        edges.push_back({u, v, w});
    }
    for (int i = 1; i <= n * 2; i++) parent[i] = i;
    sort(edges.begin(), edges.end());
    for (auto e : edges) {
        if (find(e.fr) == find(e.to) || find(e.fr + n) == find(e.to + n)) {
            printf("%d\n", e.dis);
            return 0;
        } else {
            // 把冲突关系节点放入不同的集合
            unite(e.fr + n, e.to);
            unite(e.fr, e.to + n);
        }
    }
    printf("%d", 0);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值