题目大意: 给出一张DAG,选出若干个点组成点集 V V V,满足对于任意 x , y ∈ V x,y\in V x,y∈V, x x x 不能到达 y y y 且 y y y 不能到达 x x x,最大化 ∣ V ∣ |V| ∣V∣,输出一种方案,并且输出每个点是否有可能在 V V V 内。
题解
前置知识——偏序关系及Dilworth定理。
自己手玩这题各种性质的证明还是相当有趣的
假如将 x x x 能到达 y y y 看成一个偏序关系,那么题目要求的其实就是最大反链。根据Dilworth定理,等价于求最小链划分。假如建一张新图,如果原图中 x x x 能到达 y y y,那么新图中 x x x 向 y y y 连边,最小链划分等价于新图中用最少的点不重复的链覆盖所有点。
这个问题可以看成,一开始有 n n n 条链,第 i i i 条只包含第 i i i 个点,然后尽可能多的将链合并。
考虑将新图中的点拆成入点和出点,边从出点连向入点,那么就得到了一张二分图。跑一遍最大匹配,对于一条匹配边,其实就相当于将匹配的两点所在的链收尾相接。假设最大匹配是 M M M,即合并了 M M M 次链,那么最少的链数就是 n − M n-M n−M,也就是第一问的答案。
关于第二问,先要知道如何从二分图中构造出一个最小点覆盖 S S S,参考König定理(下面就默认你看懂这个构造了),取个补集我们就得到了最大独立集,记为 A A A。
对于一个点 x x x,假如 x i n x_{in} xin 和 x o u t x_{out} xout 都在 A A A 内,那么就将它放入另一个点集 B B B,可以发现 B B B 就是一个最长反链。
证明: 对于 x ∈ B x\in B x∈B, x i n , x o u t x_{in},x_{out} xin,xout 都在 A A A 内,意味着他们就都不在 S S S 内,根据上面给的构造最小点覆盖的方法,不难证明对于任意 x , y x,y x,y, x i n / o u t , y i n / o u t x_{in/out},y_{in/out} xin/out,yin/out 之间是不存在边的,否则就不可能都不在 S S S 内,所以 B B B 是一个反链。
引理: 不可能存在一个 x x x,满足 x i n x_{in} xin 和 x o u t x_{out} xout 都不在 A A A 内。
证明: 反证法,假设存在这样的 x x x,那么这意味着 x i n x_{in} xin 和 x o u t x_{out} xout 都在 S S S 内。
(重申一下,二分图中左部点是出点,右部点是入点)若 x o u t x_{out} xout 在 S S S 内,那么至少存在这样的结构( 1 1 1 号点代表 x x x):
左边是二分图,红色边是匹配边,蓝色则是非匹配边;右边是对应的原图。
而假如 x i n x_{in} xin 在 S S S 内,那么至少存在这样的结构( 1 1 1 号点依然代表 x x x):
脑补一下,合并两张右边的新图,可以发现 4 4 4 一定可以到达 2 2 2,这意味着在二分图中,有一条 4 o u t 4_{out} 4out 往 2 i n 2_{in} 2in 的非匹配边。
那么在伪·增广的时候, 2 i n 2_{in} 2in 就会走到 4 o u t 4_{out} 4out 然后走到 1 i n 1_{in} 1in 从而标记 1 i n 1_{in} 1in,那么此时 1 i n 1_{in} 1in 就不在 S S S 内,与一开始的假设矛盾。
也就是说,对于每个 x x x, x i n x_{in} xin 和 x o u t x_{out} xout 至少一者在 A A A 内,那么对于 ∣ A ∣ − ∣ B ∣ |A|-|B| ∣A∣−∣B∣,我们可以理解为将 x i n x_{in} xin 和 x o u t x_{out} xout 都在 A A A 内的 x x x 去掉一个 x i n x_{in} xin,于是 ∣ A ∣ − ∣ B ∣ |A|-|B| ∣A∣−∣B∣ 可以理解为 对于每个 x x x, x i n x_{in} xin 和 x o u t x_{out} xout 恰好一者在 A A A 内,就显然有 ∣ A ∣ − ∣ B ∣ = n |A|-|B|=n ∣A∣−∣B∣=n。
而又由于 ∣ A ∣ = 2 n − M |A|=2n-M ∣A∣=2n−M,所以有 ∣ B ∣ = ∣ A ∣ − ( ∣ A ∣ − ∣ B ∣ ) = n − M |B|=|A|-(|A|-|B|)=n-M ∣B∣=∣A∣−(∣A∣−∣B∣)=n−M。
这不就是第一问求出来的最长反链的长度吗?于是就证完了。第二问也解决了。
而第三问就很好办,对于每个 x x x,我们看一下强制选 x x x 后,最长反链的长度,如果恰好为 n − M − 1 n-M-1 n−M−1,那么 x x x 就有可能在 S S S 内,如果小于 n − M − 1 n-M-1 n−M−1 则不可能。具体实现就是,将 x x x 以及与 x x x 相连的点全部删掉再跑二分图最大匹配。
整理一下思路: 先使用Dilworth定理转化为求最小不可重链覆盖,使用二分图最大匹配解决第一问,在二分图基础上构造出最大独立集 A A A,从 A A A 中拎出一个最大反链的方案解决第二问,然后再随便搞搞解决第三问。
去掉网络流板子看其实代码出乎意料地短:
#include <cstdio>
#include <cstring>
#include <bitset>
#include <algorithm>
using namespace std;
#define maxn 210
int n,m;
bitset<110> g[110];
struct edge{
int y,z,next;}e[100010];
int first[maxn],len=1;
void buildroad(int x,int y,int z){
e[++len]=(edge){
y,z,first[x]};first[x]=len;}
void ins(int x,int y,int z){
buildroad(x,y,z);buildroad(y,x,0);}
int S,T,q[maxn],st,ed,h[maxn],cur[maxn];
bool bfs(){
memset(h,0,sizeof(h));
q[st=ed=1]=S;h[S]=1;
while(st<=ed){
int x=q[st++];cur[x]=first[x];
for(int i=first[x];i;i=e[i].next){
int y=e[i].y;
if(!h[y]&&e[i].z)h[y]=h[x]+1,q[++ed]=y;
}
}
return h[T];
}
int dfs(int x,int flow){
if(x==T)return flow; int tt=0;
for(int &i=cur[x];i;i=e[i].next){
int y=e[i].y;
if(h[y]==h[x]+1&&e[i].z){
int p=dfs(y,min(e[i].z,flow-tt));tt+=p;
e[i].z-=p;e[i^1].z+=p;
}
if(tt==flow)break;
}
if(!tt)h[x]=0;
return tt;
}
bool del[maxn];
int solve(){
int re=0;
memset(first,0,sizeof(first));len=1;
for(int i=1;i<=n;i++)if(!del[i])
ins(S,i,1),ins(i+n,T,1),re++;
for(int i=1;i<=n;i++)if(!del[i])
for(int j=1;j<=n;j++)if(!del[j])
if(g[i][j])ins(i,j+n,1);
while(bfs())re-=dfs(S,1e9);
return re;
}
int mat[maxn];
bool vl[maxn],vr[maxn];
void DFS(int x){
vr[x]=true;
for(int i=1;i<=n;i++)
if(g[i][x]&&!vl[i])
vl[i]=true,DFS(mat[i]);
}
int main()
{
scanf("%d %d",&n,&m);S=2*n+1;T=S+1;
for(int i=1,x,y;i<=m;i++)
scanf("%d %d",&x,&y),g[x][y]=1;
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
if(g[i][k])g[i]|=g[k];
int ans;
printf("%d\n",ans=solve());
for(int i=1;i<=n;i++)if(!e[4*i].z)
for(int j=first[i+n];j;j=e[j].next)
if(e[j].z)mat[e[j].y]=i;
for(int i=1;i<=n;i++)
if(e[4*i].z)DFS(i);
for(int i=1;i<=n;i++)
putchar('0'+(!vl[i]&&vr[i]));//i_{out}未被标记和i_{in}被标记意味着他们都在A内,即i在B内
printf("\n");
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)del[j]=j==i||g[i][j]||g[j][i];
putchar('0'+(solve()==ans-1));
}
}