关押罪犯(二分图or并查集)

题目链接:
https://www.acwing.com/problem/content/259/
http://contest-hunter.org:83/contest/0x49「数据结构进阶」练习/4901 关押罪犯


题目描述

描述
S 城现有两座监狱,一共关押着N 名罪犯,编号分别为1~N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为c 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为c 的冲突事件。
每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到S 城Z 市长那里。公务繁忙的Z 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。
在详细考察了N 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。那么,应如何分配罪犯,才能使Z 市长看到的那个冲突事件的影响力最小?这个最小值是多少?
输入格式
第一行为两个正整数N 和M,分别表示罪犯的数目以及存在仇恨的罪犯对数。
接下来的M 行每行为三个正整数aj,bj,cj,表示aj 号和bj 号罪犯之间存在仇恨,其怨气值为cj。数据保证1<=aj<bj<N,0<cj<=1,000,000,000 且每对罪犯组合只出现一次。
输出格式
输出共1行,为Z 市长看到的那个冲突事件的影响力。如果本年内监狱中未发生任何冲突事件,请输出0。
样例输入
4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884
样例输出
3512
数据范围与约定
对于30%的数据有N≤ 15。
对于70%的数据有N≤ 2000,M≤ 50000。
对于100%的数据有N≤ 20000,M≤ 100000。
来源
CCF NOIP2010 T3


二分图的思路

  1. 首先可以看到我在题目中黄色打底的内些关键字,从大到小排序,那么第一个事件一定是影响最大的事件,再看一下要求,要求第一个事件的影响值最小,那么不就是要求最大的最小,心中就有了一个二分的念头,之后发现在题目中,答案是单调的(你可以从大到小二分,或者是从小到大二分),这个时候就应该想到这道题是可以二分答案的。

  2. 其次,我们想一下,如果要二分答案的话,什么使我们二分的标准呢?

对于这道题而言我们可以反着想一下:
题目上的意思是有两个监狱,而且是在监狱之间的才会对答案有影响,分别在两个监狱的对答案没有影响,答案要求的是在监狱内部的影响值最大的最小;

把答案要求的转换一下,不就是要求的是分别在两个监狱的影响值最小的最小,这样的话就可以看成是在监狱内部的没有影响,影响答案的只有在两个监狱外部的影响值

这个时候就可以联想到二分图的左部和右部,在内部没有连边,两个部分之间有连边

  1. 最后得出结论:二分监狱内部的最大影响值或者是监狱内部的最大影响值的坐标,把大于最大影响值的关系都建图,判断当前建的图是不是二分图即可。

二分图的代码

二分图的判定有两种方法:DFS和BFS

//DFS
bool check(int x,int color)
{
    c[x]=color;
    for(int i=last[x];i;i=e[i].nex)
    {
        int y=e[i].to;
        if(c[x]==c[y])  return false;
        else if(!c[y]&&!check(y,3-color))   return false;   
    }
    return true;
}
//BFS
queue<int> q;
bool check(int xx,int color)
{
	while(q.size())	q.pop();/*初始化*/
	c[xx]=color;
	q.push(xx);
	while(q.size())
	{
		int x=q.front(); q.pop();
		for(int i=last[x];i;i=e[i].nex)
		{
			int y=e[i].to;
			if(c[x]==c[y])	return false;
			if(!c[y])	c[y]=3-c[x],q.push(y);
		}
	}
	return true;
}

这里的BFS是被Chdyhsm吐槽过丑的。。。我记住了。。。

//Author:melody
//关押罪犯的二分图做法
#include<bits/stdc++.h>
using namespace std;

const int mm=100010;
const int nn=20010;

int n,m;
int c[nn]; /*染色*/

struct number
{
	int x,y,ver;
}nu[mm];

struct edge
{
	int to,ver,nex;
}e[mm*2];
int num=0;
int last[nn];

void add(int x,int y)
{
	e[++num].to=y;
	e[num].nex=last[x]; last[x]=num;
}

inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
	return x*f;
}

bool mycmp(number x,number y)/*从小到大排序*/
{
	return x.ver<y.ver;
}

queue<int> q;
bool check(int xx,int color)
{
	while(q.size())	q.pop();/*初始化*/
	c[xx]=color;
	q.push(xx);
	while(q.size())
	{
		int x=q.front(); q.pop();
		for(int i=last[x];i;i=e[i].nex)
		{
			int y=e[i].to;
			if(c[x]==c[y])	return false;
			if(!c[y])	c[y]=3-c[x],q.push(y);
		}
	}
	return true;
}

int main()
{
	n=read(),m=read();
	for(int i=1;i<=m;++i)
	{
		nu[i].x=read(),nu[i].y=read();
		nu[i].ver=read();
	}
	sort(nu+1,nu+1+m,mycmp);
	int l=0,r=m,mid;
	while(l+1<r)
	{
		memset(c,0,sizeof(c));
		num=0; 
		bool flag=true;
		memset(last,0,sizeof(last));
		mid=(l+r)>>1;
		for(int i=mid+1;i<=m;++i)
			add(nu[i].x,nu[i].y), add(nu[i].y,nu[i].x);/*把超过mid的建图*/
			
		for(int i=1;i<=n;++i)
			if(!c[i])
				if(!check(i,1))
				{
					flag=false;
					break;
				}
		if(flag)	r=mid;/*缩小范围*/
		else		l=mid;
	}
	memset(c,0,sizeof(c));
	num=0; 
	bool flag=true;
	memset(last,0,sizeof(last));
	for(int i=l+1;i<=m;++i)
		add(nu[i].x,nu[i].y), add(nu[i].y,nu[i].x);/*把超过mid的建图*/
	for(int i=1;i<=n;++i)
		if(!c[i])
			if(!check(i,1))
			{
				flag=false;
				break;
			}
	if(flag) 	printf("%d\n",nu[l].ver);
	else 		printf("%d\n",nu[r].ver);	
	return 0;
}

这里要特别鸣谢Chdy大佬和startaidou大佬对我代码查错的不懈努力,再次感谢!!!!!

而且这里要特别记录一下自己犯的sb问题:
错因
1.bfs中没有queue的初始化;
2.bfs中的queue的初始化错误,应该是while(q.size())q.pop();
但是自己打成了if(q.size()) q.pop();自己是sb吧


并查集的思路

其实一开始的时候,是没有想到并查集的的思路的,就是内种怎么都没想出来的样子。
之后经过鑫神 鑫鑫(鑫神没有鑫鑫叫的亲切,毕竟是个好看的妹子)的不懈教导,我还是只听懂了她的博客中的一种思路。。。
思路:扩展域并查集

这道题可以使并查集的扩展域+贪心的算法。

  1. 定义:对于每一个x都有一个朋友域(x_t)和敌人域(x_e);
  2. 首先要把每对关系从大到小排序,若两个还没有被记录,把(x_t)与(y_e),(x_e)与(y_t)合并【分属于不同的监狱】
  3. 之后,如果已经被记录过了,判断(x_t)与(y_t)的关系,若两个人在同一所监狱里,那么就说明最小的权值就是当前的价值,因为不可以在把两个人安排到不同的监狱来消除这次的影响。

Q:为什么这样的贪心是正确的?
A:首先是从大到小排序,保证了安排到不同监狱的的一定是对答案贡献最大的(因为把当前最大的消除了);
其次,若是不能消除的话,就是最终的答案,因为你不可能为了当前的这个较小的答案,去拆开之前的较大的答案。


并查集的代码

#include<bits/stdc++.h>
using namespace std;

const int mm=100010;
const int nn=20010;

int n,m;

struct number
{
	int x,y,ver;
}nu[mm];
 
int fa[nn*2];

inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
	return x*f;
}

bool mycmp(number x,number y)/*从大到小排序*/
{
	return x.ver>y.ver;
}

int gf(int x)
{
	if(x==fa[x])	return x;
	return fa[x]=gf(fa[x]);
}

int out()
{
	for(int i=1;i<=m;++i)
		cout<<nu[i].x<<' '<<nu[i].y<<' '<<nu[i].ver<<endl;
}

int main()
{
	n=read(),m=read();
	for(int i=1;i<=m;++i)
	{
		nu[i].x=read(), nu[i].y=read();
		nu[i].ver=read();
	}
	sort(nu+1,nu+1+m,mycmp);
	for(int i=1;i<=2*n;++i)	fa[i]=i; 
	for(int i=1;i<=m;++i)
	{
		int x=nu[i].x, x_e=x+n;
		int y=nu[i].y, y_e=y+n;
		if(gf(x)==gf(y)||gf(x_e)==gf(y_e))
		{
			printf("%d\n",nu[i].ver);
			return 0;
		}
		fa[gf(x)]=gf(y_e);
		fa[gf(x_e)]=gf(y);
	}
	printf("%d\n",0);
	return 0;
}

愿你走出半生,归来仍是少年。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值