怎么求一个偏序集的最长反链并构造方案?
注:文中的偏序集用一个 DAG 的形式描述。
给你个偏序集,要求最长反链的长度。这是一个经典问题。
要你把最长反链构造出来呢,怎么办?
参考文献
https://r-64.blog.uoj.ac/blog/623 \ https://www.luogu.com.cn/article/05a58z7z
二分图最大独立集
我们知道,一个二分图的最小点覆盖等于最大匹配,原因在下面。
我们要先求得二分图的一个最大独立集,也就是点覆盖的补集。
先随便求一个最大匹配。
考虑以下 dfs 过程:从每一个右部非匹配点出发,右
最小点覆盖,就是左边遍历到的结点集合,和右边没遍历到的结点集合的并集。
点覆盖构造的证明
假设我们得到的点集为
-
所以这个事情告诉我们,每条匹配边一定有恰好一个端点被选。由于匹配的端点两两不重复,所以 $|S| \ge m$。 -
- 每条边都被覆盖。考虑反证,假设有一条边没被覆盖,那么左端点没被 dfs 过,右端点被 dfs 过了。由于做短点没被 dfs 过,因此这个右端点一定是非匹配点。然而非匹配点连出的边一定是非匹配边,因此它的每个邻居都应当遍历过,矛盾。
同时我们也能证明上面说的“二分图的最小点覆盖等于最大匹配”的结论,因为每条匹配边至少都要选一个端点,所以
最长反链的长度
这个是比较简单且经典的。
根据 Dilworth 定理,偏序集上最长反链长度 = 最小链覆盖数。
一开始每个点都是散点,我们想要通过合并尽可能多次形成尽可能少的链。
那么我们把每个点
那么每个点在链上前驱后继至多各一个,所以合并次数就是这个二分图的最大匹配。
最小链覆盖数
构造最长反链
我们考虑求出上述二分图的最大独立集。假设最大独立集中左部点选择的集合为
最长反链构造的证明
先证明这是一个反链。
考虑反证,假设存在
再考虑证明这是最长的。
假设二分图左、右点数均为
又因为最大独立集大小为
- 满足
u_1, u_2 同时在独立集中的点数|S_1| 。 -
之和。
容易发现第二个
最长反链的长度就是
实现
P4298 部分代码。
void dfs(int u) {vis[u]=1; for(int v:bG[u])if(!vis[v])dfs(v);}
main()
{
scanf("%d%d",&n,&m);
for(int i=1,u,v;i<=m;i++)
scanf("%d%d",&u,&v),g[u].set(v);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)if(g[j][i])g[j]|=g[i];
G.init(),G.s=n*2+1,G.t=n*2+2;
for(int i=1;i<=n;i++)
G.addflow(G.s,i,1),G.addflow(i+n,G.t,1);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(g[i][j])G.addflow(i,j+n,1),id[i][j+n]=G.cnt-1;
int ans=n-G.getflow(); printf("%d\n",ans);
for(int i=1;i<=n;i++)
for(int j=n+1;j<=n*2;j++)if(id[i][j])
{if(G.E[id[i][j]].w)bG[i].push_back(j),mt[j]=1; else bG[j].push_back(i);}
for(int i=n+1;i<=n*2;i++)if(!mt[i])dfs(i);
for(int i=1;i<=n;i++)
if(!vis[i]&&vis[i+n])putchar('1'); else putchar('0'); putchar(10);
for(int i=1,new_ans;i<=n;i++)
{
new_ans=0;
for(int j=1;j<=n;j++)
flag[j]=(j!=i&&!g[i][j]&&!g[j][i]),new_ans+=flag[j];
G.init(),G.s=n*2+1,G.t=n*2+2;
for(int j=1;j<=n;j++)
G.addflow(G.s,j,1),G.addflow(j+n,G.t,1);
for(int j=1;j<=n;j++)if(flag[j])
for(int k=1;k<=n;k++)if(flag[k]&&g[j][k])G.addflow(j,k+n,1);
new_ans-=G.getflow();
if(new_ans==ans-1)putchar('1'); else putchar('0');
}
return 0;
}
也可以去看看 SPOJ-DIVREL,是个类似的题。