1. 程式人生 > >1069 關押罪犯

1069 關押罪犯

-h 全國 clock span output esc 從大到小 詳細 事件

codevs——1069 關押罪犯

洛谷——P1525 關押罪犯


2010年NOIP全國聯賽提高組

時間限制: 1 s 空間限制: 128000 KB 題目等級 : 鉆石 Diamond 題目描述 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。

並查集+貪心

因為就有兩個監獄 兩個人要麽一個監獄 要麽不一個監獄(廢話- - )
為了使ans最小 我們一定會讓ci大的組合先分開
所以按ci排一遍序 然後for每一對
如果發現他倆在一個集合 輸出ci結束
否則的話 我們希望的是把他兩個分開
即把他們分別跟對方的仇人合並 
所以我們假設每個人的仇人都是i+n 
對於i 如果另外兩個人都合並到了i+n 說明兩個人一定在一所監獄 

代碼:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 200001
#define maxn 99999999
using namespace std;
int n,m,fa[N>>1];
struct nn
{
    int x,y,z;
}a[N];
int find(int x)
{
    if(fa[x]==x) return fa[x];
    else  fa[x]=find(fa[x]);
}
int cmp(nn a,nn b)
{
    return a.z>b.z;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=2*n;i++)
      fa[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++)
    {
        int r1=find(a[i].x);
        int r2=find(a[i].y);
        if(r1==r2)
        {
            printf("%d",a[i].z);
            return 0;
        }
        fa[r1]=find(a[i].y+n);
        fa[r2]=find(a[i].x+n);
    }
    printf("0");
    return 0;
}

二分圖判定

這個題的正解是二分圖判定,但是好像這種做法比上述並查集+貪心跑得慢。。。。

對於這個題,我們先把有怨氣的所有罪犯連一條邊,表明他們不能放在同一個監獄中。

然而我們必須要把兩個罪犯放在一起,對於那種跟誰都有仇的罪犯我們要找個怨氣最小的和它放在一塊。

但是我們要怎樣找這個與她怨氣最小的罪犯呢?(我們這裏要處理出的是怨氣總值最小)

我們先用二分的方法預處理出所能承受的最小怨氣(這樣快啊)

然後我們在對於這個怨氣值判斷它的所有與他相連的邊中是否有比這個值大的,如果有,我們判斷它是否可以形成一個二分圖,不能,換一種放罪犯的方法。

代碼:

#include<queue>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 100001
using namespace std;
int n,m,x,y,z,ans,tot,C,col[N],head[N<<1];
bool flag;
struct Edge
{
    int to,dis,next;
}edge[N<<1];
struct nn
{
    int x,y,z;
}a[N];
void add(int x,int y,int z)
{
    tot++;
    edge[tot].to=y;
    edge[tot].dis=z;
    edge[tot].next=head[x];
    head[x]=tot;
}
int paint(int s)
{
    for(int i=head[s];i;i=edge[i].next)
     if(edge[i].dis>C)
        {
            if(col[edge[i].to]==col[s])  return 0;
            if(col[edge[i].to]==-1)
            {
                col[edge[i].to]=col[s]^1;
                if(!paint(edge[i].to)) return 0;
            }        
        }
    return 1;
}

int cmp(nn a,nn b)
{
    return a.z>b.z;
}
bool cheak()
{
    for(int i=1;i<=n;i++)   //對於我們二分出的C值挨個進行染色 
      if(col[i]==-1)
      {
          col[i]=0;
          if(!paint(i)) return 0;
       } 
       return 1; 
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
     {
         scanf("%d%d%d",&x,&y,&z);
         a[i].x=x,a[i].y=y,a[i].z=z;//主要是方便我們最後找最小值 
         add(x,y,z);//我們將存在怨氣的罪犯連邊,表明他們在怨氣值大的時候不能在一個監獄中 
         add(y,x,z);
     }
     sort(a,a+1+m,cmp);//我們將怨氣值從大到小排序 
     int l=0,r=m;//我們利用二分找這個怨氣的最小值 
     while(l<=r)//我們還能進行二分 
     { 
         memset(col,-1,sizeof(col));
         int mid=(l+r)>>1;
         C=a[mid].z;
         if(cheak()) ans=C,l=mid+1;//這樣說明這個數我們可以再將它減小 
         else r=mid-1;//當前值不能將這些罪犯分開 
     }
    printf("%d",ans); 
    return 0;
}

1069 關押罪犯