P1525 [NOIP 2010 提高组] 关押罪犯
种类并查集的核心思想
种类并查集通过扩展原始并查集的域来表示对象之间的多种关系(如敌对、友好等)。在本题中:
-
原始域(1~n):表示罪犯在监狱A
-
扩展域(n+1~2n):表示同一个罪犯在监狱B的"虚拟镜像"
关键算法说明:
-
贪心策略:将冲突按怨气值从大到小排序,优先处理怨气值大的冲突对。
-
并查集扩展域:
-
使用
1~n
表示罪犯在监狱A -
使用
n+1~2n
表示罪犯在监狱B -
当处理
(x,y)
时:-
merge(x, y+n)
表示如果x在A,则y必须在B -
merge(y, x+n)
表示如果y在A,则x必须在B
-
-
-
冲突检测:如果在处理某对
(x,y)
时发现find(x)==find(y)
,说明按照之前的分法,这两个罪犯已经被分到同一监狱,此时当前怨气值就是答案。 -
时间复杂度:O(Mα(N)),其中α是反阿克曼函数,可以认为是常数。
#include<bits/stdc++.h> using namespace std; const int N = 2e5 + 10; // 数组大小设为2倍罪犯数量(N≤20000) const int inf = 0x3f3f3f3f; // 定义一个无穷大常量(虽然本题未使用) struct node { int x, y, z; // 存储罪犯对:x和y是罪犯编号,z是怨气值 }; node t[N]; // 存储所有罪犯关系 int n, m; // n-罪犯数量,m-关系数量 int f[N]; // 并查集父节点数组 // 比较函数:按怨气值从大到小排序 bool cmp(node a, node b) { return a.z > b.z; } // 并查集查找函数(带路径压缩) int find(int x) { if(f[x] != x) f[x] = find(f[x]); // 路径压缩优化 return f[x]; } // 并查集合并函数 void merge(int x, int y) { int fx = find(x), fy = find(y); f[fy] = fx; // 将fy的父节点设为fx } int main() { cin >> n >> m; // 输入所有罪犯关系 for(int i = 1; i <= m; i++) cin >> t[i].x >> t[i].y >> t[i].z; // 按怨气值从大到小排序(贪心处理) sort(t + 1, t + 1 + m, cmp); // 初始化并查集:1~n表示监狱A,n+1~2n表示监狱B for(int i = 1; i <= 2 * n; i++) f[i] = i; // 处理每条关系(从怨气值大的开始处理) for(int i = 1; i <= m; i++) { int x = t[i].x, y = t[i].y; // 如果x和y已经在同一集合(即被分到同一监狱) if(find(x) == find(y)) { cout << t[i].z; // 直接输出当前怨气值(因为是排序后第一个冲突) return 0; } else { // 将x和y分到不同监狱: // x在A则y在B,x在B则y在A merge(x, y + n); // x和y的"敌人"(y+n)合并 merge(y, x + n); // y和x的"敌人"(x+n)合并 } } // 如果没有冲突发生 cout << 0; return 0; }