NKOJ 1046 【并查集】[NOIP2010-3]关押罪犯

NKOJ 1046 【并查集】[NOIP2010-3]关押罪犯

总思路

  • 对罪犯之间地关系按怨气值进行排序,排完后尽量把怨气值大的罪犯拆散。核心在于如何维护罪犯之间的敌人和在同一所监狱的关系。

方法一

思路:普通并查集

实现方法

  • 对于每一个罪犯,如果他遇到了第一个敌人用数组记录他的敌人,如果他已经有敌人了就把他的两个敌人排在一起,如果不可避免地让两个有敌对关系地罪犯排在了一起,就输出。
  • 安排罪犯的这个过程用普通并查集实现。

代码

#include<cstdio>
#include<algorithm>
#define int long long
using namespace std;
int n,m;
int f[100005],eny[100005];
int getf(int x){
	if(f[x]==x) return x;
	return f[x]=getf(f[x]);
}
struct node{int x,y,val;}arr[100005];
void merge(int x,int y){
	int fx=getf(x),fy=getf(y);
	if(fx!=fy) f[fx]=fy;
}
bool cmp(node a,node b){
	return a.val>b.val;
}
signed main(){
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++) f[i]=i;
	for(int i=1;i<=m;i++) scanf("%lld%lld%lld",&arr[i].x,&arr[i].y,&arr[i].val);
	sort(arr+1,arr+1+m,cmp);
	for(int i=1;i<=m;i++){
		if(getf(arr[i].x)==getf(arr[i].y)){
			printf("%lld",arr[i].val);
			return 0;
		}
		else{
			if(eny[arr[i].x]==0) eny[arr[i].x]=arr[i].y;
			else merge(arr[i].y,eny[arr[i].x]);
			if(eny[arr[i].y]==0) eny[arr[i].y]=arr[i].x;
			else merge(arr[i].x,eny[arr[i].y]);
		}
	}
	puts("0");

	return 0;
}

方法二

思路:种类并查集(未实现)

实现方法

  • 将数组开到原来的两倍大小,前 \(1 \sim n\) 表示朋友关系,后 \(n+1 \sim 2n\) 表示前 \(n\) 个人的分身,分身是专门用来连接敌人关系的。

如下图,展示的是有 \(4\) 个人,\(1\)\(2\) 号是朋友,\(1\)\(3\)\(3\)\(4\) 是敌人

从图中可以看出,种类并查集的好处在于可以很清楚地表达敌人的敌人是朋友的关系。

  • 在本题中,原本没有敌人的敌人是朋友的关系,但是我们希望将矛盾大的罪犯拆开,如果 A 有一个敌人 B,A 跟 B 的矛盾大,此时又有一个 B 的敌人 C 跟 B 的矛盾也很大,那么 A 在一号监狱,B在二号监狱,不可避免地将 C 也放在一号监狱,这和敌人的敌人是朋友这句话是异曲同工的。

总结

实际上,上述上述两种方法是一样的,只是表达敌人这种关系的方法不同。

posted @ 2024-12-14 10:02  hsr_ray  阅读(21)  评论(0)    收藏  举报