正题
近期做的神仙题。
偏序集合:其中有些元素之间没有大小顺序的集合,在此题中,xxx可达yyy则称x>yx>yx>y,由于是一个DAGDAGDAG,可能没有相互可达的元素,所以DAGDAGDAG可能是个偏序集合。
全序集合:所有元素都知道大小关系的集合,比如自然数集合,再比如一条链。
反链:集合内两两元素之间都没有大小关系的集合。
发现要我们求的是一个偏序集合的最长反链。
根据Dilwoth定理:最长反链的大小等于最小全序集合划分数。
在一个DAGDAGDAG中,我们先要将所有的偏序关系找出来,传递闭包就可以,然后再划分成若干条链,使链的条数最少,那么这个数量就是最长反链的大小。
划分成若干条链使用网络流解决也是十分轻松的:我们只需要对每一个点建立入点和出点,出点连流量为1的边向那些与其有偏序关系的点,流这一条边表示这个点后面新接了一个点,一开始是n个独立的点,所以用n-最大流即为最少划分链数,也就是最长反链的大小。
第一问的答案我们已经会求解了,现在我们来解决第二个问题,如何找到这个最长反链?
我们先来提出一种解法:对于右边所有没有匹配上的点,我们对其进行dfsdfsdfs,当从右往左走时,只能走非匹配边,当从左往右走时只能走匹配边:左边被dfsdfsdfs过的点与右边没有被dfsdfsdfs过的点即为这张二分图的最小点覆盖。
为什么这些点就是最小点覆盖?首先,左边被dfsdfsdfs过的节点一定是匹配点,也就是说dfs的结束一定在右边,否则会找出一条增广路,而dfsdfsdfs时,除了第一个点为右边的非匹配点,其他点都是成对的匹配点。对于右边非匹配点而言,如果有边,那么左边的匹配点会将这条边覆盖;对于右边的匹配点来说,如果没有被dfsdfsdfs到就可以自己覆盖,否则就一定是从左边的匹配点dfsdfsdfs过来的,所以也一定会被覆盖到。对于左边的匹配点来说,如果没有被dfsdfsdfs到,则说明不存在与右边非匹配点的连边,右侧的对应的匹配点也没有被dfsdfsdfs到,会被覆盖。对于左边的非匹配点而言,他只与右边的匹配点有连边,而且哪些点也不会被dfsdfsdfs到,否则出现增广路。
而且这东西等于最大匹配数,因为会发现每一个匹配都会选一个端点。
最小点覆盖的补集就是最大独立集。
对于一个点,如果它的入点和出点都在最大独立集内,那么它就在最长反链里面。
首先如果入点和出点都在最大独立集里面,说明它与其它选中的点没有偏序关系。
我们只需要证明这个数量是对的就行:
设总共有nnn个点,匹配数为mmm,则最大独立集的大小为2n−m2n-m2n−m,它的大小又等于左边在最大独立集里面或者右边在最大独立集里面的点数+左边和右边都在最大独立集里面的点数,前者最多不超过nnn,可知后者最小为n−mn-mn−m,而最长反链大小为n−mn-mn−m,所以它就等于n−mn-mn−m。
第三问是问一个点是否可能在最长反链里面,我们只需要将它与和它有偏序关系的点取出来,跑一次最长反链,看看是否只减少了1就可以了。
#include<bits/stdc++.h>
using namespace std;
const int N=105;
struct edge{
int y,nex,c;
}s[11010];
int first[N<<1],len=0,n,m,S,T,d[N<<1],head[N<<1],qs[N<<1],st,ed,num;
bool f[N][N],tf[N],v[N<<1];
bool bfs(){
memset(d,0,sizeof(d));d[S]=1;
qs[st=ed=1]=S;
for(int i=1;i<=T;i++) head[i]=first[i];
while(st<=ed){
int x=qs[st++];
for(int i=first[x];i!=0;i=s[i].nex) if(s[i].c && !d[s[i].y])
d[s[i].y]=d[x]+1,qs[++ed]=s[i].y;
}
return d[T]!=0;
}
int dfs(int x,int t){
if(x==T) return t;
int tot=0;
for(int&i=head[x];i!=0;i=s[i].nex) if(s[i].c && d[s[i].y]==d[x]+1){
int my=dfs(s[i].y,min(t-tot,s[i].c));
tot+=my;s[i].c-=my;s[i^1].c+=my;
if(t==tot) break;
}
return tot;
}
int Dinic(){
int tot=0,dx;
while(bfs()){
dx=dfs(S,1e9);
while(dx) tot+=dx,dx=dfs(S,1e9);
}
return tot;
}
void ins(int x,int y,int c){
s[++len]=(edge){y,first[x],c};first[x]=len;
s[++len]=(edge){x,first[y],0};first[y]=len;
}
void inss(){
memset(first,0,sizeof(first));len=1;
for(int i=1;i<=n;i++) if(!tf[i])
for(int j=1;j<=n;j++) if(!tf[j] && f[i][j])
ins(i,j+n,1);
for(int i=1;i<=n;i++) ins(S,i,1),ins(n+i,T,1);
}
void gs(int x){
v[x]=true;
for(int i=first[x];i!=0;i=s[i].nex) if(!s[i].c && s[i].y<=2*n && !v[s[i].y])
gs(s[i].y);
}
int main(){
scanf("%d %d",&n,&m);S=2*n+1,T=2*n+2;
int x,y;
for(int i=1;i<=m;i++)
scanf("%d %d",&x,&y),f[x][y]=true;
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
f[i][j]|=(f[i][k]&f[k][j]);
inss();
int ans=n-Dinic();
printf("%d\n",ans);
for(int i=1;i<=n;i++) if(!s[len-(n-i)*4].c) gs(n+i);
for(int i=1;i<=n;i++) if(!v[i] && v[n+i]) printf("1");else printf("0");printf("\n");
for(int i=1;i<=n;i++){
memset(tf,false,sizeof(tf));
int tot=0;
for(int j=1;j<=n;j++) if(j==i || f[i][j] || f[j][i]) tf[j]=true,tot++;
inss();
printf("%d",n-tot-Dinic()==ans-1);
}
}
博客围绕偏序集合的最长反链问题展开。介绍了偏序集合、全序集合和反链的概念,依据Dilwoth定理,利用网络流求解最长反链大小。还阐述了如何找到最长反链,通过二分图最小点覆盖的补集最大独立集来确定。最后说明判断点是否在最长反链的方法。
920

被折叠的 条评论
为什么被折叠?



