CTSC 2008 祭祀 题解

题目传送门

题目大意: 给出一张DAG,选出若干个点组成点集 V V V,满足对于任意 x , y ∈ V x,y\in V x,yV 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 nM,也就是第一问的答案。

关于第二问,先要知道如何从二分图中构造出一个最小点覆盖 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 xB 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| AB,我们可以理解为将 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| AB 可以理解为 对于每个 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 AB=n

而又由于 ∣ A ∣ = 2 n − M |A|=2n-M A=2nM,所以有 ∣ B ∣ = ∣ A ∣ − ( ∣ A ∣ − ∣ B ∣ ) = n − M |B|=|A|-(|A|-|B|)=n-M B=A(AB)=nM

这不就是第一问求出来的最长反链的长度吗?于是就证完了。第二问也解决了。

而第三问就很好办,对于每个 x x x,我们看一下强制选 x x x 后,最长反链的长度,如果恰好为 n − M − 1 n-M-1 nM1,那么 x x x 就有可能在 S S S 内,如果小于 n − M − 1 n-M-1 nM1 则不可能。具体实现就是,将 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));
	}
}

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/114107535