Noip2010 关押罪犯
SOl1:开虚点的方法
并查集的精义在于物以类聚,信息的传递性。
即A认识B,B认识C,则A认识C。
对于本题1与2有冲突,那我们自然要将它们分开才行。
如果1又与3冲突,那3自然要与2放在一个房间了。至于是放哪个房间,不是重点。
发现每个人与其它人的关系,只有两种即有冲突,没有冲突。
于是我们可以对每个人开两个集合出来,一个集合放与之没有冲突的,另一个集合放与之有冲突的。
这样便引出的虚点的概念来了。
于是1开出1号点,与1+N号点,2开出2号点,与2+N号点。
接下来1与2有冲突,于是1与N+2号点放一个集合,2与N+1号点放一个集合。这样就完整的表述了1与2有冲突这个事情。
于是这样一直放下去,直到当发现发生某个冲突的两个人,居然在一起集合里面(在一个集合说明它们是和平相处的),则说明这个冲突是无法避免的了。
/*
此题还可以用二分答案加二分图染色的方法来做
二分答案是指二分一下,前多少条冲突规则是可以接受的
二分图染色是说,对于一个图,我们将一个点染成黑色,则这条边的
另一个点染成白色。反之亦然,如果染好一个点后,发现与之相连的
另一个点染的色,居然与之相同,则染色失败
*/
#include<bits/stdc++.h>
using namespace std;
struct Node
{
int x,y,z;
}e[200020];
int tot,s,n,m;
int head[20005],b[20005],a[100005];
bool f;
int cmp(int a,int b){return a<b;}
void insert(int x,int y,int z)
{
e[++tot].x=y;
e[tot].y=z;
e[tot].z=head[x];
head[x]=tot;
}
void dfs(int x,int data)
{
if(f==false) //如果已染色失败,则退出
return;
b[x]=data;//将x这个点染成data色
for(int i=head[x];i!=0;i=e[i].z) //遍历所有与x相连的边
{
if(e[i].y>s)
//注意这一句,并不是所有的边都要走的
//这也是前面为什么要记下每条边冲突值的原因
{
if(b[e[i].x]==-1) //如果这个点没有被染色
dfs(e[i].x,1-data); //染成另一种颜色
else
if(b[e[i].x]==data) //如果已被染了,且与当前点一样,则染色失败
{
f=false;
return;
}
}
}
}
bool work(int midd)
{
memset(b,-1,sizeof(b));
f=true;//假设能染色成功
s=a[midd];//记下第midd条冲突的怨气值
for(int i=1;i<=n;i++)
if(b[i]==-1)
//注意前midd条规则所产生的,可能是一个森林,
//于是当发现一个点没有被染色,就对它进行染色
dfs(i,0);
return f;
}
int main()
{
cin>>n>>m;
memset(head,0,sizeof(head));
int x,y,z;
tot=0;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
insert(x,y,z);
insert(y,x,z);
a[i+1]=z; //记下这条边的冲突值,在后面有用到的
}
a[1]=0; //有可能所有冲突都可以避免,所以要有边权的0的。
sort(a,a+m+1,cmp);//对冲突值进行升序排列
int l=1,r=m+1,mid;
while(l<r) //二分答案,二分一个冲突编号出来,以它的冲突值为最小值,将值大于它的边进行构图。
{
mid=(l+r)/2;
if(work(mid)==true) //染色成功,说明冲突值还可以于变小一点,于是右边界移动。
r=mid;
else
l=mid+1;
}
cout<<a[l];
}

浙公网安备 33010602011771号