题意
给你 N ( N ≤ 2 × 1 0 4 ) N(N \leq 2\times10^4) N(N≤2×104)个节点和 M ≤ 1 0 5 M \leq 10^5 M≤105对冲突关系,当这些关系中的点被分配到同一个集合的时候会产生大小为 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) (u→1),和 ( v → 0 ) (v \rightarrow 0) (v→0),或者 ( u → 0 ) (u \rightarrow 0) (u→0),和 ( v → 1 ) (v \rightarrow 1) (v→1)。只要发现 ( u → 1 ) (u \rightarrow 1) (u→1), ( v → 1 ) (v \rightarrow 1) (v→1)或者 ( u → 0 ) (u \rightarrow 0) (u→0), ( v → 0 ) (v \rightarrow 0) (v→0)被分配到了一个同一个集合那么就说明冲突避免不了了。表示属于不同集合的方法也很简单,因为 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;
}