【NOIP2010】【 关押罪犯】(并查集)/ (二分答案+二分图染色)

版权声明:本人原创,未经许可,不得转载 https://blog.csdn.net/qq_42505741/article/details/84110309

题目:

题目描述

SS城现有两座监狱,一共关押着NN名罪犯,编号分别为1-N1−N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为cc 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为cc的冲突事件。

每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到S 城Z 市长那里。公务繁忙的Z 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。

在详细考察了NN 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。

那么,应如何分配罪犯,才能使Z 市长看到的那个冲突事件的影响力最小?这个最小值是多少?

输入输出格式

输入格式:

每行中两个数之间用一个空格隔开。第一行为两个正整数N,MN,M,分别表示罪犯的数目以及存在仇恨的罪犯对数。接下来的MM行每行为三个正整数a_j,b_j,c_jaj​,bj​,cj​,表示a_jaj​ 号和b_jbj​号罪犯之间存在仇恨,其怨气值为c_jcj​。数据保证1<aj≤bj≤N ,0 < cj≤ 1,000,000,0001<aj≤bj≤N,0<cj≤1,000,000,000,且每对罪犯组合只出现一次。

输出格式:

共11 行,为ZZ 市长看到的那个冲突事件的影响力。如果本年内监狱中未发生任何冲突事件,请输出00。

输入输出样例

输入样例#1: 复制

4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884

输出样例#1: 复制

3512

说明

【输入输出样例说明】罪犯之间的怨气值如下面左图所示,右图所示为罪犯的分配方法,市长看到的冲突事件影响力是35123512(由22 号和33 号罪犯引发)。其他任何分法都不会比这个分法更优。

【数据范围】

对于30\%30%的数据有N≤ 15N≤15。

对于70\%70%的数据有N≤ 2000,M≤ 50000N≤2000,M≤50000。

对于100\%100%的数据有N≤ 20000,M≤ 100000N≤20000,M≤100000。

解题报告:这道题目是在算法进阶上看到的,尝试着解决了一下用并查集的做法,(主要是想训练二分染色)但是还是失败了,第一次见到并查集的这种做法,很奇妙。

题意是什么不多解释了,并查集解法:首先咱们应该要明确一个观点就是优先将大的边(即仇恨值大的两个人,放在两个集合里)之后如果遇到边不能放进去了,(放进去之后是矛盾的)就说明没能放进去的边是最大的。仇恨值大的已经逐步消失,到最后不能消失的就是剩下的满足条件的叫警长看到的最大边。中间涉及了一个敌人数组,如果他们是可以放到两个数组里边的话,那么咱们就把二者互设为敌人,敌人的敌人就是朋友。。。

ac代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long ll;

const int maxn =1e5+100;
int far[maxn],erm[maxn];

struct node{
	int x,y,v;
}num[maxn];
bool cmp(node a,node b)
{
	return a.v>b.v;
}

int find (int x)
{
	if(x==far[x])
		return far[x];
	else
		return far[x]=find(far[x]);
}

void merge(int a,int b)
{
	int pa=find(a);
	int pb=find(b);
	far[pb]=pa;
}
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		far[i]=i;
	for(int i=1;i<=m;i++)
	{
		cin>>num[i].x>>num[i].y>>num[i].v;
	}
	sort(num+1,num+1+m,cmp);
	for(int i=1;i<=m;i++)
	{
		int tmp1=find(num[i].x);
		int tmp2=find(num[i].y);
		if(tmp1==tmp2)
		{
			printf("%d\n",num[i].v);
			return 0;
		}
		if(erm[num[i].x]==0)
			erm[num[i].x]=num[i].y;
		if(erm[num[i].y]==0)
			erm[num[i].y]=num[i].x;
		merge(num[i].x,erm[num[i].y]);
		merge(num[i].y,erm[num[i].x]);	
	}
	printf("0\n");
} 

二分染色解法:咱们一开始就将边进行排序,利用二分查找,优先判断这个中点能不能实现将所有的点都成功染色,如果可以的话,咱们就向左继续二分,从而找到最小的能够满足全部染色的边,如果不可以的话,就向右继续二分,至于为啥,我也不知道啊!!,不过这样是可以保证咱们二分的协调性的,能够有跳出的条件去实现它,至于是否能够全部成功染色是写的递归函数,每次都去寻找没有染色的点去染色,如果可以的话就继续进行下去,直到成功将所有点都弄好为止(如果中间找不到能够继续的点,就结束)(第一次写,有错误请大佬指出)

ac代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long ll;

const int maxn=2e5+1000;
const int maxnn=1e5+1000;
int n,m,flag;
struct node{
	int v,w,next;
}e[maxn];
bool cmp(int  a,int  b)
{
	return a>b;
}
int cnt=0;
int head[maxn];

void add_edg(int u,int v,int w)
{
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].next=head[u];
	head[u]=cnt;
}
int col[maxn];
int W;
int w[maxn];
int ans;

bool bi(int u)
{
	for(int i=head[u];i;i=e[i].next)
	{
		if(e[i].w<=W) continue;
		int v=e[i].v;
		if(col[u]==col[v]) return false;
		if(!col[v])
		{
			col[v]=3-col[u];//进行染色,成功 
			if(!bi(v)) return false;	//v结点的后续不能够染色了 
		}
	}
	return true;
}
bool dfs(int u,int fa)
{
	for(int i=head[u];i;i=e[i].next)
	{
		if(e[i].w<=W) continue;
		int v=e[i].v;
		if(v==fa) continue;
		if(col[u]==col[v]) return false;
		if(!col[v])
		{
			col[v]=3-col[u];
			if(!dfs(v,u)) return false;
		}
	}
	return true;
}

bool check()
{
	for(int i=1;i<=n;i++)
	{
		if(!col[i])//如果i点没有染色 
		{
			col[i]=1;
			if(!bi(i)) return false;//如果i点不能够继续向下染色了  结束 
		}
	}
	return true;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		add_edg(a,b,c);
		add_edg(b,a,c);
		w[i]=c;
	}//建图 
	sort(w+1,w+1+m);//按照边长进行排序 
	int l=1,r=m;
	while(l<r)//二分寻找最佳 
	{
		int m=(l+r)/2;
		W=w[m];//中值 
		memset(col,0,sizeof(col));
		if(check())//check进行染色 
		{
			ans=w[m];//如果可以的话,存值,然后向左遍历 ,寻扎是否有更小的值满足 
			r=m;
		}
		else
		 	l=m+1;//向右跑 ,左边不能够实现 
	}
	if(l==1&&r<l) cout<<0;
	else  printf("%d",ans); 
}

猜你喜欢

转载自blog.csdn.net/qq_42505741/article/details/84110309
今日推荐