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

题意:给你一张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-ans);
31     return 0;
32 }
原文地址:https://www.cnblogs.com/HocRiser/p/9309332.html