[网络流24题]最小路径覆盖问题
Description
给定有向图$G=(V,E)$。设$P$是$G$的一个简单路(顶点不相交)的集合。如果$V$中每个顶点恰好在$P$的一条路上,则称$P$是$G$的一个路径覆盖。$P$中路径可以从$V$的任何一个顶点开始,长度也是任意的,特别地,可以为$0$。$G$的最小路径覆盖是$G$的所含路径条数最少的路径覆盖。
设计一个有效算法求一个有向无环图$G$的最小路径覆盖。
Input
第$1$行有$2$个正整数$n$和$m$。$n$是给定有向无环图$G$的顶点数,$m$是$G$的边数。
接下来的$m$行,每行有$2$个正整数$i,j$,表示一条有向边$(i,j)$。
Output
从第$1$行开始,每行输出一条路径。
最后一行是最少路径数。
Sample Input
11 12
1 2
1 3
1 4
2 5
3 6
4 7
5 8
6 9
7 10
8 11
9 11
10 11
Sample Output
1 4 7 10 11
2 5 8
3 6 9
3
HINT
$n\;\leq\;150$
Solution
对于一个路径覆盖,有如下性质:
- 每个顶点$i$属于且只属于一个路径。
- 路径上除终点外,从每个顶点出发只有一条边指向路径上的另一顶点。
所以我们可以把每个顶点拆成两个顶点,一个是出发点$x_i$,一个是目标点$y_i$,建立二分图模型。该二分图的任何一个匹配方案,都对应了一个路径覆盖方案。如果匹配数为$0$,那么显然路径数=顶点数。每增加一条匹配边,那么路径覆盖数就减少一个,所以路径数=顶点数-匹配数。要想使路径数最少,则应最大化匹配数,所以要求二分图的最大匹配。
#include<cmath> #include<ctime> #include<queue> #include<stack> #include<cstdio> #include<vector> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define N 305 #define M 46000 using namespace std; struct graph{ int nxt,to,f; }e[M]; int a[N],g[N],dep[N],n,m,l,s,t,ans,cnt=1; bool v[N]; queue<int> q; inline void addedge(int x,int y,int f){ e[++cnt].nxt=g[x];g[x]=cnt;e[cnt].to=y;e[cnt].f=f; } inline void adde(int x,int y,int f){ addedge(x,y,f);addedge(y,x,0); } inline bool bfs(int u){ memset(dep,0,sizeof(dep)); dep[u]=1;q.push(u); while(!q.empty()){ u=q.front();q.pop(); for(int i=g[u];i;i=e[i].nxt) if(e[i].f>0&&!dep[e[i].to]){ q.push(e[i].to); dep[e[i].to]=dep[u]+1; } } return dep[t]; } inline int dfs(int u,int f){ int ret=0; if(u==t) return f; for(int i=g[u],d;i&&f;i=e[i].nxt) if(e[i].f>0&&dep[e[i].to]>dep[u]){ d=dfs(e[i].to,min(f,e[i].f)); e[i].f-=d;e[i^1].f+=d;ret+=d;f-=d; } return ret; } inline int dinic(){ int ret=0; while(true){ if(!bfs(s)) return ret; ret+=dfs(s,N); } } inline void find(int u){ a[++l]=u; for(int i=g[u];i;i=e[i].nxt) if(!e[i].f&&!v[e[i].to-n]){ v[e[i].to-n]=true;find(e[i].to-n); } } inline void Aireen(){ scanf("%d%d",&n,&m); for(int i=1,j,k;i<=m;++i){ scanf("%d%d",&j,&k); adde(j,k+n,1); } s=(n<<1)+1;t=s+1; for(int i=n;i;--i) adde(s,i,1); for(int i=1;i<=n;++i) adde(i+n,t,1); ans=n-dinic(); v[s]=v[t]=true; for(int i=1;i<=n;++i) if(!v[i]){ l=0;v[i]=true;find(i); for(int j=1;j<=l;++j) printf("%d ",a[j]); printf("\n"); } printf("%d\n",ans); } int main(){ freopen("path.in","r",stdin); freopen("path.out","w",stdout); Aireen(); fclose(stdin); fclose(stdout); return 0; }