您的位置:首页 > 其它

noip2010关押罪犯 两种方法 并查集和二分

2015-07-29 11:41 351 查看
题目描述 Description

S 城现有两座监狱,一共关押着N 名罪犯,编号分别为1~N。他们之间的关系自然也极
不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨
气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之
间的积怨越多。如果两名怨气值为c 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并
造成影响力为c 的冲突事件。
每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,
然后上报到S 城Z 市长那里。公务繁忙的Z 市长只会去看列表中的第一个事件的影响力,
如果影响很坏,他就会考虑撤换警察局长。
在详细考察了N 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在
两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只
要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。那
么,应如何分配罪犯,才能使Z 市长看到的那个冲突事件的影响力最小?这个最小值是少?

输入描述 Input Description

第一行为两个正整数N 和M,分别表示罪犯的数目以及存在仇恨的罪犯对数。
接下来的M 行每行为三个正整数aj,bj,cj,表示aj 号和bj 号罪犯之间存在仇恨,其怨气值为cj。数据保证,且每对罪犯组合只出现一次。

输出描述 Output Description

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

样例输入 Sample Input

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

样例输出 Sample Output

3512

数据范围及提示 Data Size & Hint

罪犯之间的怨气值如下面左图所示,右图所示为罪犯的分配方法,市长看到的冲突事件
影响力是3512(由2 号和3 号罪犯引发)。其他任何分法都不会比这个分法更优。
【数据范围】
对于30%的数据有N≤ 15。
对于70%的数据有N≤ 2000,M≤ 50000。
对于100%的数据有N≤ 20000,M≤ 100000。

蒟蒻只会写并查集~~~~(>_<)~~~~

步骤:

1.按照从大到小的顺序排序影响值,并将每个人的父亲设为自己(并查集基本操作,不会的去学)

2.依次判断

(1)若两个人在相同集合,则他们的影响值一定是最大的。

      //即若将两人放到不同的集合,则必与之前的某对冲突,因为是按照从大到小排序的,则前面的影响值一定大于后面,所以当前影响值满足最大值最小。

 (2)若两个人均有对立集合,则合并x与 y的对立集合,且合并 y 与 x的对立集合。

 (3)若两个人中有任意一人有对立集合,则合并 对立集合和没对立集合的罪犯,并将没对立集合的罪犯的对立集合设为另一罪犯。

 (4)若两个人均无对立集合,则将彼此设为对立集合。

3.若此时程序还未退出,则输出0。

贴出代码:

<span style="font-size:32px;">#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
struct Node{
int x,y,z;
}a[100005];
int n,m,e[20001],f[20001];
int cmp(Node x,Node y)
{
return x.z>y.z;
}
int Find(int x)
{
return x==f[x]? x:f[x]=Find(f[x]);
}
void Union(int x,int y)
{
int fx=Find(x);
int fy=Find(y);
f[f[x]]=fy;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
f[i]=i;
for(int i=1;i<=m;i++)
scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
sort(a+1,a+1+m,cmp);

for(int i=1;i<=m;i++)
{
if(Find(a[i].x)==Find(a[i].y))
{
printf("%d\n",a[i].z);
return 0;
}
if(e[a[i].x]!=0&&e[a[i].y]!=0)
{
Union(e[a[i].x],a[i].y);
Union(a[i].x,e[a[i].y]);
}
else
if(e[a[i].x]!=0&&e[a[i].y]==0)
{
e[a[i].y]=a[i].x;
Union(e[a[i].x],a[i].y);
}
else
if(e[a[i].x]==0&&e[a[i].y]!=0)
{
e[a[i].x]=a[i].y;
Union(e[a[i].y],a[i].x);
}
else
if(e[a[i].x]==0&&e[a[i].y]==0)
{
e[a[i].x]=a[i].y;
e[a[i].y]=a[i].x;
}
}
printf("0\n");
return 0;
}</span>


补充:二分法求答案

前向星存储信息,用a数组单独储存怨气值,增加0(若无冲突,则输出0。

将a数组从小到大排序,二份答案。

若当前大于等于mid的关系能满足二分图的要求(用到二分图染色判定),则说明解小于等于mid,r=mid,否则说明解大于mid,就将L赋值为mid+1。

下面是代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct Node{
int y,z,next;
}e[200020];
int tot,item,n,m;
int head[20005],b[20005],a[100005];
bool pd;
int cmp(int a,int b){return a<b;}
void insert(int x,int y,int z)
{
e[++tot].y=y;
e[tot].z=z;
e[tot].next=head[x];
head[x]=tot;
}
void dfs(int x,int data)
{
if(pd==false)return;
b[x]=data;
for(int i=head[x];i!=0;i=e[i].next)
{
if(e[i].z>item)
{
if(b[e[i].y]==-1)dfs(e[i].y,1-data);
else
if(b[e[i].y]==data){pd=false;return;}
}
}
}
bool work(int midd)
{
memset(b,-1,sizeof(b));
pd=true;
item=a[midd];
for(int i=1;i<=n;i++)
if(b[i]==-1)dfs(i,0);
return pd;
}
int main()
{
scanf("%d%d",&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;
}
// for(int i=1;i<=tot;i++)cout<<e[i].y<<' '<<e[i].z<<' '<<e[i].next<<endl;
a[1]=0;
sort(a+1,a+1+m,cmp);
// for(int i=1;i<=m+1;i++)cout<<a[i]<<' ';
// cout<<endl;
int l=1,r=m+1,mid;
while(l<r)
{
mid=(l+r)/2;
// cout<<l<<' '<<r<<' '<<mid<<endl;
if(work(mid)==true)r=mid;
else l=mid+1;
}
printf("%d\n",a[l]);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: