题目

求有向无环图的最小路径覆盖(可以从任意一个点开始)


题目链接:洛谷P 2764


二分图性质:最小路径覆盖 == 顶点数 - 最大匹配数

所以此题我们可以转化为求最大匹配来做,但是麻烦的是输出路径。


考虑建图:

我们可以把点拆成两个点,分别为入点和出点,让超级源点S连向每个点的入点,让每个点的出点连向超级汇点T,然后如果u能到达v,那么就让u的入点连向v的出点即可。(建图完成)

然后跑一遍最大流,答案就是总的点数 n - dinic()

考虑输出路径:

我们输出路径,主要就是要找到每个边集合开始的边,很多人都用并查集来维护边的关系,但是我觉得完全没有必要,我们考虑一下开始的边的关系,我们可以发现如果当前的点的出点所连接的点,如果没有流量流过来,那么这个点就是开始的点。所以我们可以判断每个点的出点,如果所有点到它的流量都是1,然后我们dfs一遍输出即可。


AC代码:

#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=310,M=120010;
int n,m,h[N],s,t,vis[N],f[N];
int head[N],to[M],w[M],nex[M],tot=1;
inline void ade(int a,int b,int c){
	to[++tot]=b; w[tot]=c; nex[tot]=head[a]; head[a]=tot;
}
inline void add(int a,int b,int c){
	ade(a,b,c);	ade(b,a,0);
}
int find(int x){
	return x==f[x]?x:f[x]=find(f[x]);
}
int bfs(){
	memset(h,0,sizeof h);	queue<int> q;	q.push(s);	h[s]=1;
	while(q.size()){
		int u=q.front();	q.pop();
		for(int i=head[u];i;i=nex[i]){
			if(w[i]&&!h[to[i]]){
				h[to[i]]=h[u]+1;	q.push(to[i]);
			}
		}
	}
	return h[t];
}
int dfs(int x,int f){
	if(x==t)	return f;
	int fl=0;
	for(int i=head[x];i&&f;i=nex[i]){
		if(w[i]&&h[to[i]]==h[x]+1){
			int mi=dfs(to[i],min(w[i],f));
			w[i]-=mi;	w[i^1]+=mi;	fl+=mi;	f-=mi;
		}
	}
	if(!fl)	h[x]=-1;
	return fl;
}
int dinic(){
	int res=0;
	while(bfs())	res+=dfs(s,0x3f3f3f3f);
	return res;
}
void out(int x){
	printf("%d ",x);
	for(int i=head[x*2-1];i;i=nex[i]){
		if(!w[i]&&to[i]!=s)	out(to[i]/2);
	}
}
signed main(){
	cin>>n>>m;	s=0,t=2*n+2;
	for(int i=1;i<=n;i++){
		add(s,i*2-1,1);	add(i*2,t,1);
	}
	for(int i=1;i<=m;i++){
		int a,b;	cin>>a>>b;	add(a*2-1,b*2,1);
	}
	int res=n-dinic();
	for(int i=1;i<=n;i++){
		int flag=0;
		for(int j=head[i*2];j&&!flag;j=nex[j]){
			if(to[j]!=t&&!w[j^1])	flag++;
		}
		if(!flag)	out(i),puts("");
	}
	cout<<res<<endl;
	return 0;
}