洛谷-关押罪犯-NOIP2010提高组复赛

题目描述

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。

输入输出样例

输入样例#1:
4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884
输出样例#1:
3512

说明

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

【数据范围】对于30%的数据有N≤ 15。对于70%的数据有N≤ 2000,M≤ 50000。对于100%的数据有N≤ 20000,M≤ 100000。

思路1:

考虑用二分答案+二分图判断

我们不难想到,a与b有c这么多的矛盾,则可以说a与b间有权重为c的边,这样就构成了有n个顶点m条边的无向图。

将罪犯分配到两个监狱中,不难想到是二分图。

排序罪犯的怒气值c,进行二分查找,对于当前找到的这个怒气值(边)mid,我们将图中比这条mid边权重小或等于的边暂时删去,判断剩下的图能否构成一个二分图,如果构成则当前的这个怒气值mid即为所求,输出结束程序即可,不要忘了如果没有任何冲突事件发生则输出0.

拓展:二分图判断——染色法

从其中一个顶点开始,将跟它邻接的点染成与其不同的颜色,如果邻接的点有相同颜色的,则说明不是二分图,每次用bfs遍历即可

判断代码如下(源自:https://blog.csdn.net/zhangxian___/article/details/73699241):

 1 #include <queue>
 2 #include <cstring>
 3 #include <iostream>
 4 using namespace std;
 5 
 6 const int N=510;
 7 int color[N],graph[N][N];
 8 
 9 //0为白色,1为黑色
10 bool bfs(int s, int n)
11 {
12     queue<int> q;
13     q.push(s);
14     color[s]=1;
15     while(!q.empty())
16     {
17         int from=q.front();
18         q.pop();
19         for(int i=1;i<=n;i++)
20         {
21             if(graph[from][i]&&color[i]==-1)
22             {
23                 q.push(i);
24                 color[i]=!color[from];//染成不同的颜色
25             }
26             if(graph[from][i]&&color[from]==color[i]) return false;//颜色有相同,则不是二分图
27         }
28     }
29     return true;
30 }
31 
32 int main() 
33 {  
34     int n,m,a,b,i;
35     memset(color,-1,sizeof(color));
36     cin>>n>>m;
37     for(i=0;i<m;i++)
38     {
39         cin>>a>>b;
40         graph[a][b]=graph[b][a]=1;
41     }
42     bool flag=false;
43     for(i=1;i<=n;i++)
44         if(color[i]==-1&&!bfs(i,n)) //遍历各个连通分支
45         {
46             flag=true;
47             break;
48         }  
49     if(flag)
50         cout<<"NO"<<endl;
51     else
52         cout<<"YES"<<endl;
53     return 0;
54 }

思路2:

考虑用并查集+贪心思想

这个方法较上面的方法容易理解,贪心地:我们希望怒气值很大的两个罪犯不在同一个监狱,如遇到两个罪犯不得不在一个监狱时,这时候输出的结果即为最大值。

我们按照并查集路径优化的思想,两个监狱各选出一个头子,这样我们在判断两个罪犯是否要放在两个监狱时,只有看看他们之前是否就归附于同一个头子,如果之前两人都归附于同一个监狱的头子,输出就OK,否则安排他俩进两个监狱。

按照怒气值从大到小依次取出两个罪犯,看看他们能否放在两个监狱:

如果能放,必须要满足不归附于同一个头子。

如果不能放,则安排他俩进进不同监狱,然后两个罪犯分别当上两个监狱的头子(即让原来的监狱头子归附于当前a与b)。

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <math.h>
 4 #include <algorithm>
 5 #include <iostream>
 6 using namespace std;
 7 typedef struct conflict//存储矛盾信息 
 8 {
 9     int a,b,c;//a与b有c这么多的矛盾(怒气) 
10 }conflict;
11 
12 int against[20002]={0};//against[i]存储i的敌人 
13 int father[20002];//记录这个节点的父亲 
14 conflict infermation[100002];//记录信息数组 
15 
16 bool cmp(conflict x,conflict y)//排序函数 
17 {
18     return x.c>y.c;
19 }
20 
21 int find(int x)//寻找x节点的头子(并查集+路径压缩) 
22 {
23     if(x!=father[x]) father[x]=find(father[x]);
24     return father[x];
25 }
26 
27 int main()
28 {
29     int n,m;
30     int i,j;
31     int x,y;//x为a的祖先,y为b的祖先 
32     //freopen("prison.in","r",stdin);
33     //freopen("prison.out","w",stdout);
34     scanf("%d%d",&n,&m);
35     for(i=1;i<=m;i++)//输入信息 
36     {
37         scanf("%d%d%d",&infermation[i].a,&infermation[i].b,&infermation[i].c);
38     }
39     sort(infermation+1,infermation+m+1,cmp);//按矛盾值从大到小排序结构体 
40     for(i=1;i<=n;i++)
41     {
42         father[i]=i;//并查集初始化(自己是自己的父亲) 
43     }
44     for(i=1;i<=m;i++)//从大到小取出矛盾值判断 
45     {
46         if(find(infermation[i].a)==find(infermation[i].b))//如果两个罪犯已经在一个监狱了(他们都归附于同一个头子),肯定是最优值,输出,结束程序 
47         {
48             printf("%d\n",infermation[i].c);
49             return 0;//直接结束程序 
50         } 
51         if(!against[infermation[i].a])//如果a没有敌人 
52         {
53             against[infermation[i].a]=infermation[i].b;//那么b归入a的敌人中 
54         }
55         else
56         {
57             father[find(against[infermation[i].a])]=father[infermation[i].b];//否则把b和a的敌人分在一起,即a的敌人头子指向b的父亲 
58         }
59         if(!against[infermation[i].b])//如果b没有敌人 
60         {
61             against[infermation[i].b]=infermation[i].a; //那么a归入b的敌人中 
62         }        
63         else
64         {
65             father[find(against[infermation[i].b])]=father[infermation[i].a];//否则把a和b的敌人分在一起,即b的敌人头子指向a的父亲 
66         }    
67     }
68     printf("0\n");//没有冲突找到则输出0 
69     return 0;
70 }

猜你喜欢

转载自www.cnblogs.com/geek-007/p/9905351.html