[模板] P4298 [CTSC2008]祭祀 (模板--最长反链)

P4298 [CTSC2008]祭祀 (模板--最长反链)

最长反链

\(def:\)

在有向无环图中,存在一个点的集合,这个集合中两个点谁都走不到谁。这个集合叫做最长反链

可以理解为 \(DAG\) 的最大独立集

其中:有向无环图的最长反链等于最小(少)链覆盖

其实真正的 \(Dilworth\) 定理是这样的:

对于任意有限偏序集,其最大反链中元素的数目必等于最小链划分中链的数目

只要跑一个传递闭包,最小链覆盖的问题就转化为了最小路径覆盖的问题。

void floyd(){
	for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++){
				if(G[i][k] && G[k][j])G[i][j]=1;
			} 
}

最小链覆盖与最小路径覆盖的关系和相互转化

  • 唯一区别是一个可以重复经过点,一个不可重复
  • 最小路径覆盖 \(=n-\)最大匹配

其中最大匹配也是 \(DAG\) 的最小点覆盖

通过传递闭包,就把可以重叠路径的条件,转化为了偏序集中的最小路径覆盖

所以最终答案就是偏序集的最小路径覆盖

祭祀

第一问已经解决。

第二问:在反链最长的情况下,每个点是否可以成为独立集的一个点

  • 可以把和当前点有关系的所有点都删去(可以理解为把这个连通块删去)

  • 如果答案减少了 \(1\) ,说明这个连通块用一条链就可以覆盖,那么这个点是最佳选项

  • 如果这个点不是最佳选项,答案减少量会 \(>1\)

for(int id=1;id<=n;id++){
		clear();
		int Cnt=0,nn=n;
		for(int i=1;i<=n;i++)if(G[id][i] || G[i][id] || i==id)ban[i]=true,nn--;
		for(int i=1;i<=n;i++){
			memset(vis,false,sizeof vis);
			Cnt+=dfs(i);
		}
		able[id]=((n-cnt)==(nn-Cnt)+1);
	}

第三问:染色法提供一种方案

  • 首先这些点必须是最优的(在第二问满足题意的)

  • 我们对这个连通块进行染色,遍历到这个连通块的点时直接 \(continue\)

for(int i=1;i<=n;i++){
		if(col[i] || !able[i])continue;//由于是尽可能多的,所以必须在第三问可以选到 
		++Col;
		chose[i] = true;
		for(int j=1;j<=n;j++)if((G[i][j] || G[j][i] || i==j) && !col[j])col[j]=Col;
	}

所以三个问题都解决了,至于结论的证明可以参考:小粉兔的博客

所以总体代码长这样:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 110 , maxm = 1010;
int n,m;
int G[maxn][maxn];
bool vis[maxn],ban[maxn];
int match[maxn];
bool dfs(int u){
	if(ban[u])return false;
	for(int i=1;i<=n;i++){
		if(G[u][i] && !vis[i]){
			if(ban[i])continue;
			vis[i]=true;
			if(!match[i] || dfs(match[i])){
				match[i]=u;return true;
			}
		}
	}
	return false;
}
int col[maxn],Col;
bool able[maxn],chose[maxn];
void floyd(){
	for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++){
				if(G[i][k] && G[k][j])G[i][j]=1;
			}
}
inline void clear(){
	memset(ban,false,sizeof ban);	
	memset(match,0,sizeof match);
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int u,v;scanf("%d%d",&u,&v);
		G[u][v]=1; 
	}
	floyd();//传递闭包
	int cnt=0;
	for(int i=1;i<=n;i++){
		memset(vis,false,sizeof vis);cnt+=dfs(i);
	}
	printf("%d\n",n-cnt);
	for(int id=1;id<=n;id++){
		clear();
		int Cnt=0,nn=n;
		for(int i=1;i<=n;i++)if(G[id][i] || G[i][id] || i==id)ban[i]=true,nn--;
		for(int i=1;i<=n;i++){
			memset(vis,false,sizeof vis);
			Cnt+=dfs(i);
		}
		able[id]=((n-cnt)==(nn-Cnt)+1);
	}
	for(int i=1;i<=n;i++){
		if(col[i] || !able[i])continue;//由于是尽可能多的,所以必须在第三问可以选到 
		++Col;
		chose[i] = true;
		for(int j=1;j<=n;j++)if((G[i][j] || G[j][i] || i==j) && !col[j])col[j]=Col;
	}
	for(int i=1;i<=n;i++)putchar(chose[i]?'1':'0');
	puts("");
	for(int i=1;i<=n;i++)putchar(able[i]?'1':'0');
	return 0;
}
posted @ 2021-08-12 17:19  ¶凉笙  阅读(35)  评论(0编辑  收藏  举报