1. 程式人生 > >[BZOJ1143][CTSC2008]祭祀river(Dilworth定理+二分圖匹配)

[BZOJ1143][CTSC2008]祭祀river(Dilworth定理+二分圖匹配)

algorithm 傳遞閉包 轉化 進一步 freopen eof memset nbsp 二分圖

題意:給你一張n個點的DAG,最大化選擇的點數,是點之間兩兩不可達。

要從Dilworth定理說起。

Dilworth定理是定義在偏序集上的,也可以從圖論的角度解釋。偏序集中兩個元素能比較大小,則在圖中連一條有向邊。

定義反鏈為一個點集,滿足集合中的點兩兩不可達。

Dilworth定理:最小路徑覆蓋=最長反鏈。

證明:http://vfleaking.blog.163.com/blog/static/1748076342012918105514527/

例子:NOIP1999第二問:給定一個數列a,將其分成若幹個子序列,使每個子序列都是下降子序列,並最小化分的子序列個數。

對於所有i<j且a[i]>a[j],從i向j連邊,則問題轉化成求圖的最小路徑覆蓋,根據Dilworth定理進一步轉化為求最長反鏈,而這裏的最長反鏈即是最長不降子序列的長度。所以這題只要求一個最長不降子序列即可。

回到這道題,就是要求一個最長反鏈。這裏任意兩個可達的點之間都需要連一條邊,所以需要求傳遞閉包。

然後通過Dilworth定理轉化為求DAG的最小路徑覆蓋,這就是二分圖匹配的經典問題了。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 5 using namespace std;
 6 
 7 const int N=110;
 8 int
n,m,u,v,ans,lk[N]; 9 bool mp[N][N],a[N][N],vis[N]; 10 11 bool find(int x){ 12 rep(i,1,n) if (!vis[i] && mp[x][i]){ 13 vis[i]=1; 14 if (!lk[i] || find(lk[i])) { lk[i]=x; return 1; } 15 } 16 return 0; 17 } 18 19 int main(){ 20 freopen("bzoj1143.in","
r",stdin); 21 freopen("bzoj1143.out","w",stdout); 22 scanf("%d%d",&n,&m); 23 rep(i,1,m) scanf("%d%d",&u,&v),mp[u][v]=1; 24 rep(k,1,n) rep(i,1,n) rep(j,1,n) mp[i][j]|=mp[i][k]&mp[k][j]; 25 rep(i,1,n) rep(j,1,n) if (i!=j && mp[i][j]) a[i][j]=1; 26 rep(i,1,n){ 27 memset(vis,0,sizeof(vis)); 28 if (find(i)) ans++; 29 } 30 printf("%d\n",n-ans); 31 return 0; 32 }

[BZOJ1143][CTSC2008]祭祀river(Dilworth定理+二分圖匹配)