[BZOJ1143][CTSC2008]祭祀river 做題筆記

題目來源:http://www.lydsy.com/JudgeOnline/problem.php?id=1143
其實說是原創,也就是代碼而已。。。
一種理解,二分圖的最大獨立集(引用自http://www.cnblogs.com/BLADEVIL/p/3713043.html):

  我們可以將一個點拆成兩個點x,y,那麼如果存在一條i->j的路徑,我們就連接xi,yj,那麼答案就是n-最大匹配數。

  因爲i->j所以對於i與j只能選一個,那麼我們只需要求出來二分圖的最大獨立集就可以了,二分圖的最大獨立集=點數-最大匹配。

另一種思路是最長反鏈(引用自http://www.bubuko.com/infodetail-664202.html,不知道原作者是誰真是太抱歉了)。

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

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

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

那麼很顯然這道題就是求最長反鏈長度了。

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

對偶定理:最長鏈長度 = 最小反鏈覆蓋

那麼我們要求出的就是這個有向無環圖的最小鏈覆蓋了。最小鏈覆蓋也就是路徑可以相交的最小路徑覆蓋。

我們先來看路徑不能相交的最小路徑覆蓋怎麼來做:

建立一個二分圖,兩邊都是n個點,原圖的每個點 i 對應兩個,在左邊的叫做 i1, 在右邊的叫做 i2 。

然後原圖中如果存在一條邊 (x, y),那麼就在二分圖中建立 (x1, y2) 的邊。

這樣建立二分圖之後,原圖的點數 n - 二分圖最大匹配 = 原圖的最小路徑覆蓋(路徑不能相交)。

這樣爲什麼是對的呢?我們可以認爲,開始時原圖的每個點都是獨立的一條路徑,然後我們每次在二分圖中選出一條邊,就是將兩條路徑連接成一條路徑,答案數就減少1。

而路徑是不能相交的,所以我們在二分圖中選出的邊也是不能相交的,所以就是二分圖的最大匹配。

瞭解了路徑不能相交的最小路徑覆蓋之後,怎麼解路徑可以相交的最小路徑覆蓋(也就是最小鏈覆蓋)呢?

我們將原圖做一次Floyd傳遞閉包,之後就可以知道任意兩點 x, y,x 是否能到達 y。

如果兩個點 x, y,滿足 x 可以到達 y ,那麼就在二分圖中建立邊 (x1, y2) 。

這樣其實就是相當於將原圖改造了一下,只要 x 能到達 y ,就直接連一條邊 (x,
y),這樣就可以“繞過”原圖的一些被其他路徑佔用的點,直接構造新路徑了。

這樣就將可以相交的最小路徑覆蓋轉化爲了路徑不能相交的最小路徑覆蓋了。


#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int inf=0x3fffffff;
int map[205][205];
int maxflow=0,s,t,n,m,tot=1;
int d[400];
int head[400],e[160000],nxt[160000],ver[160000];
void add (int u,int v,int w) {
    ver[++tot]=v;e[tot]=w;nxt[tot]=head[u];head[u]=tot;
    ver[++tot]=u;e[tot]=0;nxt[tot]=head[v];head[v]=tot;
}
void floyd () {
    int i,j,k;
    for (k=1;k<=n;k++)
        for (i=1;i<=n;i++) 
            for (j=1;j<=n;j++) 
                map[i][j]|=(map[i][k]&map[k][j]);
}
bool bfs () {
    queue <int> q;
    memset(d,0,sizeof(d));
    q.push(s); d[s]=1;
    while (!q.empty()) {
        int x=q.front(); q.pop();
        for (int i=head[x];i;i=nxt[i])
            if (e[i]&&!d[ver[i]]) {
                q.push(ver[i]);
                d[ver[i]]=d[x]+1;
                if (ver[i]==t) return 1;
            }
    }
    return 0;
}
int dinic (int x,int f) {
    int rest=f;
    if (x==t) return f;
    for (int i=head[x];i&&rest;i=nxt[i]) 
        if (e[i]&&d[ver[i]]==d[x]+1) {
            int now=dinic(ver[i],min(e[i],rest));
            if (!now) d[ver[i]]=0;
            e[i]-=now;
            e[i^1]+=now;
            rest-=now;
        }
    return f-rest;
}
void build () {
    for (int i=1;i<=n;i++) 
        add(s,i,1),add(i+n,t,1);
    for (int i=1;i<=n;i++) 
        for (int j=1;j<=n;j++) 
            if (map[i][j]) add(i,j+n,inf);
}
int main () {
    int tmp,u,v;
    scanf("%d%d",&n,&m);
    s=0,t=2*n+1;
    for (int i=1;i<=m;i++) {
        scanf("%d%d",&u,&v);
        map[u][v]=1;
    }
    floyd();
    build();
    while (bfs()) 
        while (tmp=dinic(s,inf)) maxflow+=tmp;
    printf("%d\n",n-maxflow);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章