1. 程式人生 > >【bzoj1143: [CTSC2008]祭祀river】有向無環圖的最長反鏈

【bzoj1143: [CTSC2008]祭祀river】有向無環圖的最長反鏈

1143: [CTSC2008]祭祀river

Time Limit: 10 Sec  Memory Limit: 162 MB
Submit: 3192  Solved: 1632
[Submit][Status][Discuss]

Description

  在遙遠的東方,有一個神祕的民族,自稱Y族。他們世代居住在水面上,奉龍王為神。每逢重大慶典, Y族都 會在水面上舉辦盛大的祭祀活動。我們可以把Y族居住地水系看成一個由岔口和河道組成的網路。每條河道連線著 兩個岔口,並且水在河道內按照一個固定的方向流動。顯然,水系中不會有環流(下圖描述一個環流的例子)。

 

  由於人數眾多的原因,Y族的祭祀活動會在多個岔口上同時舉行。出於對龍王的尊重,這些祭祀地點的選擇必 須非常慎重。準確地說,Y族人認為,如果水流可以從一個祭祀點流到另外一個祭祀點,那麼祭祀就會失去它神聖 的意義。族長希望在保持祭祀神聖性的基礎上,選擇儘可能多的祭祀的地點。

Input

  第一行包含兩個用空格隔開的整數N、M,分別表示岔口和河道的數目,岔口從1到N編號。接下來M行,每行包 含兩個用空格隔開的整數u、v,描述一條連線岔口u和岔口v的河道,水流方向為自u向v。 N ≤ 100 M ≤ 1 000

Output

  第一行包含一個整數K,表示最多能選取的祭祀點的個數。

Sample Input

4 4
1 2
3 4
3 2
4 2

Sample Output

2

【樣例說明】
在樣例給出的水系中,不存在一種方法能夠選擇三個或者三個以上的祭祀點。包含兩個祭祀點的測試點的方案有兩種:
選擇岔口1與岔口3(如樣例輸出第二行),選擇岔口1與岔口4。
水流可以從任意岔口流至岔口2。如果在岔口2建立祭祀點,那麼任意其他岔口都不能建立祭祀點
但是在最優的一種祭祀點的選取方案中我們可以建立兩個祭祀點,所以岔口2不能建立祭祀點。對於其他岔口
至少存在一個最優方案選擇該岔口為祭祀點,所以輸出為1011。

icpc上居然出了這個原題,就爆炸了,回來補一補二分圖和有向無環圖的東西。

1、二分圖最大匹配

匈牙利演算法O(V*E)的複雜度內可求出二分圖的最大匹配

2、二分圖最小點覆蓋

找到點數最少的一個點集,使得二分圖裡的所有邊至少有一個端點是在該點集中

最小點覆蓋=最大匹配

3、二分圖最小邊覆蓋

找到邊數最少的一個邊集,使得二分圖裡的所有點至少是一條邊的端點

最小邊覆蓋=圖中點的個數-最大匹配

證明:把最大匹配的邊都加入,則剩下的點之間都沒有邊,那麼再把剩下的點都連線起來,邊數=最大匹配+圖中點的個數-2*最大匹配=圖中點的個數-最大匹配

4、二分圖最大獨立集

找到一個點數最大的點集,使得點集裡的所有點對之間在原圖裡沒有邊。

最大獨立集=圖中點的個數-最大匹配

證明:把所有點加入點集中,最大獨立集問題就轉化為刪去最少的點使得最終的點集是個獨立集,那麼 最大獨立集=圖中點的個數-最小點覆蓋=圖中點的個數-最大匹配

最小不相交路徑覆蓋:每一條路徑經過的頂點各不相同。如,最小路徑覆蓋數為31->3>425

最小可相交路徑覆蓋:每一條路徑經過的頂點可以相同。如,最小路徑覆蓋數為21->3->42->3>5

5、有向無環圖最小不相交路徑覆蓋

用最少的不相交路徑覆蓋所有頂點。

將有向無環圖中的各點拆點,i點拆為ai,bi  ,對於一條邊:i->j,轉化為 ai->bj, 便可把有向無環圖轉化為二分圖。

有向無環圖最小不相交路徑覆蓋=圖中點的個數-最大匹配

證明:剛開始每個點為一條路徑,則開始時有n條路徑,對於二分圖中的每一條匹配邊就代表把兩條路徑變為一條路徑,則最後的答案就是圖中點的個數-最大匹配

6、有向無環圖最小可相交路徑覆蓋

用最少的可相交路徑覆蓋所有頂點。

處理出有向無環圖中所有點的到達情況,拆點,若i點可經過一條路徑到達j點,那麼連一條ai->bj 的邊,此時有向圖邊轉化為二分圖,問題也就轉化為5、有向無環圖最小不相交路徑覆蓋。

在有向無環圖中,有如下的一些定義和性質:

鏈:一條鏈是一些點的集合,鏈上任意兩個點x, y,滿足要麼 x 能到達 y ,要麼 y 能到達 x

反鏈:一條反鏈是一些點的集合,鏈上任意兩個點x, y,滿足 x 不能到達 y,且 y 也不能到達 x

一個定理:最長反鏈長度 = 最小鏈覆蓋(用最少的鏈覆蓋所有頂點)

對偶定理:最長鏈長度 = 最小反鏈覆蓋 ///????這個是什麼意思沒看懂,有人看懂了評論下

最小鏈覆蓋也就是最小可相交路徑覆蓋。


經過上面一些亂七八糟的介紹,可以知道,這道題目就是求最長反鏈長度,就是求最長反鏈長度,就是求有向無環圖最小可相交路徑覆蓋,見(6)

求各點的聯通性可以用Floyd 也可以直接搜。

#include<cstdio>
#include<cstring>
#define M 1005
#define N 105
using namespace std;
int k,K,fir[N],Fir[N],n,m,l,r,sign[N],link[N],vis[N];
struct he{
    int r,nx;
}A[N*N],a[M];
void add(int l,int r){
    k++;a[k].r=r;a[k].nx=fir[l];fir[l]=k;
}
void Add(int l,int r){
    K++;A[K].r=r;A[K].nx=Fir[l];Fir[l]=K;
}
void dfs(int x,int f){
    vis[x]=1;
    if(x!=f)Add(f,x);
    for(int i=fir[x];i!=-1;i=a[i].nx)
    if(!vis[a[i].r]){
        dfs(a[i].r,f);
    }
}
bool check(int x){
    for(int i=Fir[x];i!=-1;i=A[i].nx)
    if(!sign[A[i].r]){
        sign[A[i].r]=1;
        if(link[A[i].r]==0){
            link[A[i].r]=x;
            return true;
        }
        if(check(link[A[i].r])){
            link[A[i].r]=x;
            return true;
        }
    }
    return false;
}
int hungery(){
    int ans=n;
    for(int i=1;i<=n;i++){
        memset(sign,0,sizeof(sign));
        if(check(i)) ans--;
    }
    return ans;
}
int main(){
    memset(fir,-1,sizeof(fir));
    memset(Fir,-1,sizeof(Fir));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&l,&r);
        add(l,r);
    }
    for(int i=1;i<=n;i++){
        memset(vis,0,sizeof(vis));
        dfs(i,i);
    }
    printf("%d",hungery());
}