题意:有n名罪犯,m个关系,每个关系(u, v, w)表示犯人u和v如果关在同一个监狱之间会产生怨气值w,现在有两个监狱,问你怎么放置着n个犯人,犯人之间最大怨气值最小,求这个值。N≤ 20000,M≤ 100000
思路:(点击打开链接)维护罪犯在哪一个监狱不方便,我们可以维护某两个罪犯是不是在一个监狱。考虑到并查集的本职工作是维护某两点在一个集合,不能很好地处理不在一个集合的情况,通过保存某个点的“敌人”集合来代表和他不在一个监狱的罪犯,间接地实现维护某两点不在一个集合的情况。在加入关系的时候进行判断,如果某两点已经在一个集合,说明他们无论如何也安排不到不同的两个监狱了,输出仇恨值即可;如果不在一个集合,就将犯人和对方的敌人合并到一个集合。
(敌人的敌人就是朋友)
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
struct node
{
int u, v, w;
bool operator <(const node &a) const
{
return w > a.w;
}
}a[maxn];
int pre[maxn], en[maxn], n, m;
int Find(int x)
{
int r = x;
while(pre[r] != r) r = pre[r];
int i = x, j;
while(i != r)
{
j = pre[i];
pre[i] = r;
i = j;
}
return r;
}
void join(int x, int y)
{
int a = Find(x);
int b = Find(y);
if(a != b)
pre[b] = a;
}
int main(void)
{
while(cin >> n >> m)
{
for(int i = 1; i <= n; i++)
pre[i] = i, en[i] = 0;
for(int i = 1; i <= m; i++)
scanf("%d%d%d", &a[i].u, &a[i].v, &a[i].w);
sort(a+1, a+1+m);
int ans = 0;
for(int i = 1; i <= m; i++)
{
int x = a[i].u;
int y = a[i].v;
int fa = Find(x);
int fb = Find(y);
if(fa == fb)
{
ans = a[i].w;
break;
}
if(en[x]) join(y, en[x]);
else en[x] = y;
if(en[y]) join(x, en[y]);
else en[y] = x;
}
printf("%d\n", ans);
}
return 0;
}
用并查集表示每个犯人之间的关系,在同一个集合中则说明两人在同一个监狱,反之则不在同一个监狱。
用类似于克鲁斯卡尔的方法,先将边排序,然后按照仇恨值的大小从大到小处理。对于每一组,先看两个人能不能加入不同的集合,
如果可以,将两人加入不同的集合,如果不可以,则输出该组仇恨值。
用补集来表示两个点不在一个集合中。如a和b'在同一个集合中,则a和b不在同一个集合中,不能把b直接加入另一个集合,因为会对后面的结果产生影响。
如,3和4不在同一个集合中,但是我们现在不知道究竟是3在第一个监狱还是4在第一个监狱,所以此时用3和4‘在同一个集合中来表示3和4不在同一个集合中。