1. 程式人生 > >洛谷-關押罪犯-NOIP2010提高組複賽

洛谷-關押罪犯-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 }