noip 2010 关押罪犯 二分答案+二分图染色 || 并查集
题目链接
题目描述
S 城现有两座监狱,一共关押着N 名罪犯,编号分别为1~N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为c 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为c 的冲突事件。
每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到S 城Z 市长那里。公务繁忙的Z 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。
在详细考察了N 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。
那么,应如何分配罪犯,才能使Z 市长看到的那个冲突事件的影响力最小?这个最小值是多少?
思路
法一:二分答案+二分图染色
如果这是一个二分图,那么就能达到完全不发生冲突事件的效果。
所以,我们可以考虑在原图上删去若干条边得到一个二分图。在这个二分图中,是没有冲突事件发生的。然后再加上刚刚删去的边,必然是在染成同一个颜色的点之间加边,因此所加的这些边就代表着会发生的冲突事件。
因此,就要使得删去的边的权值最小。而在之后所有加进去的边中只有权值最大的那条边有意义,因为是它决定了所有冲突事件中最大的影响力,权值比它小的边加进去也无关紧要。
因为具有单调性,可以先排序再二分答案删去的最大的一条边,将小于它的边全部删去然后\(check\).
法二:并查集
思路很巧妙。也是因为这个我才想明白了用并查集判断二分图是什么原理。
这道题最根本的想法其实就是要将怒气值最大的一对对犯人分开,而并查集是用来处理等价关系的,并且不等关系不具有传递性,怎么办呢?
那就将不等关系转化为等价关系。
将\(i+n\)看作\(i(1\leq i\leq n)\)的敌人。那么在分开\(u,v\)时,说明\(u\)是\(v\)的敌人,因此\(u\)和\(v+n\)是一类,\(v\)和\(u+n\)是一类,于是可以将它们分别合并。如果哪次发现要分开的两个点已经在同一个集合中,就说明这两个点已经分不开了。因为是从大往小合并,当前这条边的权值就是会发生的冲突的最大值。
比较
从具体实现可见,法一要做\(logn\)次并查集,而法二只要做一次并查集。
因为出发点是不同的,法一是从二分图染色出发,而法二本身就是并查集的思路。可见二分图和并查集关系紧密,以后要多想想是否能够进行转化。
Code
Ver. 1
#include <bits/stdc++.h>
#define maxn 40010
#define maxm 100010
using namespace std;
typedef long long LL;
int fa[maxn], sz[maxn];
struct node {
int u, v, val;
bool operator < (const node& nd) const {
return val < nd.val;
}
}a[maxm];
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
void unionn(int x, int y) {
x = find(x), y = find(y);
if (x == y) return;
if (sz[x] > sz[y]) swap(x, y);
fa[x] = y, sz[y] += sz[x];
}
int n, m;
bool check(int id) {
for (int i = 1; i <= (n<<1); ++i) sz[i] = 1, fa[i] = i;
for (int i = id+1; i < m; ++i) {
if (find(a[i].u) == find(a[i].v)) return false;
unionn(a[i].u, a[i].v+n);
unionn(a[i].v, a[i].u+n);
}
return true;
}
int main() {
freopen("prison.in", "r", stdin);
freopen("prison.out", "w", stdout);
scanf("%d%d", &n, &m);
for (int i = 0; i < m; ++i) scanf("%d%d%d", &a[i].u, &a[i].v, &a[i].val);
sort(a, a+m);
int l = -1, r = m-1;
while (r > l) {
int mid = l+r>>1;
if (check(mid)) r = mid;
else l = mid+1;
}
if (~l) printf("%d\n", a[l].val);
else printf("0\n");
return 0;
}
Ver.2
#include <bits/stdc++.h>
#define maxn 40010
#define maxm 100010
using namespace std;
typedef long long LL;
int fa[maxn], sz[maxn];
struct node {
int u, v, val;
bool operator < (const node& nd) const {
return val > nd.val;
}
}a[maxm];
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
void unionn(int x, int y) {
x = find(x), y = find(y);
if (x == y) return;
if (sz[x] > sz[y]) swap(x, y);
fa[x] = y, sz[y] += sz[x];
}
int n, m;
int main() {
scanf("%d%d", &n, &m);
for (int i = 0; i < m; ++i) scanf("%d%d%d", &a[i].u, &a[i].v, &a[i].val);
sort(a, a+m);
for (int i = 1; i <= (n<<1); ++i) sz[i] = 1, fa[i] = i;
for (int i = 0; i < m; ++i) {
if (find(a[i].u) == find(a[i].v)) { printf("%d\n", a[i].val); exit(0); }
unionn(a[i].u, a[i].v+n);
unionn(a[i].v, a[i].u+n);
}
printf("0\n");
return 0;
}