[模板] 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;
}