把罪犯拆成两个'域',用扩展域并查集维护'分居两狱'的约束;怨气值降序处理仇恨边,第一条无法分居的边其怨气值就是答案。
OJ: roj
题目 ID: 3122
难度:提高
标签:并查集扩展域二分图判定贪心
创建: 2026-10-01 18:16
更新: 2026-10-10 11:30
形式化题目
给出
求一种划分方案,使得所有冲突的最大影响力最小,输出这个最小值;若不存在任何冲突则输出
数据范围:
正解
思路
朴素做法。 最直接的想法是枚举每个罪犯去哪座监狱(
关键观察一:答案只由"某条边被迫同狱"决定。 设答案为
关键观察二:"分开所有怨气值大的对"本质是二分图判定。 把怨气值
扩展域并查集(“敌人的敌人是朋友”)。 把每个罪犯
:" 在第一监狱"这个身份; :" 在第二监狱"这个身份。
约束 “
- 合并
与 ( 在一狱 在二狱); - 合并
与 ( 在一狱 在二狱)。
一个并查集分量里所有"
算法流程: 仇恨边按怨气值降序排序,逐条处理:
- 若
:这条边无法再分开,它的怨气值 就是答案,直接输出(更大的边都已被合法安置,之后也不会产生更大的冲突)。 - 否则合并
、 ,继续处理下一条。
所有边都处理完仍未冲突,说明图可以完全二分,答案为
用样例走一遍。
| 步骤 | 边 |
检查 | 动作(合并后同色块绑定) |
|---|---|---|---|
| 1 | 合并 |
||
| 2 | 合并 |
||
| 3 | 合并 |
||
| 4 | 被迫同狱 → 输出 3512 |
步骤 3 之后,
这正体现了算法的正确性:输出的一定是降序扫描中第一条无法满足的边,比它大的边都已安置,因此这就是最大冲突的最小值。
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-10 11:30
* update_at: 2026-10-10 11:30
*/
// 关押罪犯:扩展域并查集,按怨气值从大到小处理每条仇恨边。
#include <cstdio>
#include <vector>
#include <algorithm>
typedef long long ll;
const ll MAXN = 20005;
struct Edge {
ll a, b, c;
};
Edge edges[MAXN * 5];
ll fa[MAXN * 2]; // 扩展域:1..n 第一监狱域,n+1..2n 第二监狱域
bool edge_greater(const Edge& x, const Edge& y) {
return x.c > y.c;
}
// 路径减半的迭代式查根
ll find(ll x) {
while (fa[x] != x) {
fa[x] = fa[fa[x]];
x = fa[x];
}
return x;
}
int main() {
ll n, m;
if (scanf("%lld %lld", &n, &m) != 2) return 0; // 空输入安全返回
for (ll i = 0; i < m; i++) {
scanf("%lld %lld %lld", &edges[i].a, &edges[i].b, &edges[i].c);
}
std::sort(edges, edges + m, edge_greater); // 怨气值降序
for (ll i = 0; i <= 2 * n; i++) {
fa[i] = i;
}
for (ll i = 0; i < m; i++) {
ll a = edges[i].a, b = edges[i].b, c = edges[i].c;
ll ra = find(a), rb = find(b);
if (ra == rb) { // 冲突不可避免,c 即市长看到的最小最大值
printf("%lld\n", c);
return 0;
}
fa[ra] = find(b + n); // a 与 b+n 同盟 => a、b 分居两狱
fa[rb] = find(a + n);
}
printf("0\n"); // 所有仇恨都能化解
return 0;
}"""
关押罪犯(ROJ 3122):扩展域并查集,按怨气值从大到小处理边。
核心:二分图判定思想 —— "不能同狱"的关系要拆到两个集合里。
把每个罪犯 i 拆成 i(第一监狱域) 和 i+n(第二监狱域)。
怨气值降序处理每条仇恨边 (a,b,c):
- 若 find(a) == find(b):a、b 被迫同狱,c 就是答案(更大的 c 都已合法分配)。
- 否则合并 find(a)~find(b+n)、find(b)~find(a+n)(a、b 分居两狱)。
"""
import sys
def main() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
n, m = next(data), next(data)
# 仇恨边列表,怨气值降序
edges = sorted(((next(data), next(data), next(data)) for _ in range(m)), key=lambda e: -e[2])
# 扩展域:1..n 为第一监狱域,n+1..2n 为第二监狱域
fa = list(range(2 * n + 1))
def find(x: int) -> int:
while fa[x] != x:
fa[x] = fa[fa[x]] # 路径减半
x = fa[x]
return x
for a, b, c in edges:
ra, rb = find(a), find(b)
if ra == rb: # 冲突不可避免,c 即市长看到的最小最大值
print(c)
return
fa[ra] = find(b + n) # a 与 b+n 同盟 => a、b 分居两狱
fa[rb] = find(a + n)
print(0) # 所有仇恨都能化解
main()复杂度
- 排序
;并查集近似 ( 是阿克曼反函数,可视为常数)。总时间 ,对 绰绰有余。 - 扩展域数组大小
,空间 。
总结
- 答案 = 按怨气值降序扫描时第一条"无法分居"的仇恨边的怨气值;全部可分则答案为
。 - "分居"约束用扩展域并查集表达:
与 两个域,合并 、 ; 即被迫同狱。 - 正确性核心是单调性:大怨气值全部安置后,第一次失败处就是最优的最大冲突,无需二分。
- 对比另一常见写法(二分答案 + 染色判二分图),扩展域并查集一次排序加一次扫描即可,实现更短、常数更小。