P2764 最小路径覆盖问题(网络流24题之一)
题目描述
«问题描述:
给定有向图G=(V,E)。设P 是G 的一个简单路(顶点不相交)的集合。如果V 中每个顶点恰好在P 的一条路上,则称P是G 的一个路径覆盖。P 中路径可以从V 的任何一个顶点开始,长度也是任意的,特别地,可以为0。G 的最小路径覆盖是G 的所含路径条数最少的路径覆盖。设计一个有效算法求一个有向无环图G 的最小路径覆盖。提示:设V={1,2,.... ,n},构造网络G1=(V1,E1)如下:
每条边的容量均为1。求网络G1的( 0 x , 0 y )最大流。
«编程任务:
对于给定的给定有向无环图G,编程找出G的一个最小路径覆盖。
输入输出格式
输入格式:
件第1 行有2个正整数n和m。n是给定有向无环图G 的顶点数,m是G 的边数。接下来的m行,每行有2 个正整数i和j,表示一条有向边(i,j)。
输出格式:
从第1 行开始,每行输出一条路径。文件的最后一行是最少路径数。
输入输出样例
说明
1<=n<=150,1<=m<=6000
由@zhouyonglong提供SPJ
Solution:
先简单的解释一下最小路径覆盖:大致就是在一个有向无环图中,用最少多少条简单路径能将所有的点覆盖(简单路径简单来说就是一条路径不能和其他路径有重复的点,当然也可以认为单个点是一条简单路径)。
仔细思考,容易发现有些类似于二分图匹配的问题,异曲同工。
算法:把原图的每个点V拆成Vx和Vy两个点,如果有一条有向边A->B,那么就加边Ax−>By。这样就得到了一个二分图。那么最小路径覆盖=原图的结点数-新图的最大匹配数。
证明:一开始每个点都是独立的为一条路径,总共有n条不相交路径。我们每次在二分图里找一条匹配边就相当于把两条路径合成了一条路径,也就相当于路径数减少了1。所以找到了几条匹配边,路径数就减少了多少。所以有最小路径覆盖=原图的结点数-新图的最大匹配数。
因为路径之间不能有公共点,所以加的边之间也不能有公共点,这就是匹配的定义。
方法一:二分图匹配。综合上述所说的,我们可以直接建图,然后跑匈牙利算法,输出的话只需将所匹配的点依次输出就ok了。
代码(copy一份):
1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef long long LL; 4 const int maxn = 305; 5 const int maxm = 20005; 6 struct Edge 7 { 8 int v; 9 Edge *next; 10 }E[maxm], *H[maxn], *edges; 11 int res[maxn]; 12 int vis[maxn]; 13 void addedges(int u, int v) 14 { 15 edges->v = v; 16 edges->next = H[u]; 17 H[u] = edges++; 18 } 19 void init() 20 { 21 edges = E; 22 memset(H, 0, sizeof H); 23 memset(res, -1, sizeof res); 24 } 25 bool find(int u) 26 { 27 for(Edge *e = H[u]; e; e = e->next) if(!vis[e->v]) { 28 int v = e->v; 29 vis[v] = 1; 30 if(res[v] == -1 || find(res[v])) { 31 res[v] = u; 32 return true; 33 } 34 } 35 return false; 36 } 37 int n, m; 38 vector<int> ans; 39 int to[maxn]; 40 void work() 41 { 42 int u, v; 43 for(int i = 1; i <= m; i++) { 44 scanf("%d%d", &u, &v); 45 addedges(u, v); 46 } 47 int result = 0; 48 for(int i = 1; i <= n; i++) { 49 memset(vis, 0, sizeof vis); 50 if(find(i)) result++; 51 } 52 memset(to, 0, sizeof to); 53 for(int i = 1; i <= n; i++) if(res[i] != -1) to[res[i]] = i; 54 for(int i = 1; i <= n; i++) if(res[i] == -1) { 55 ans.clear(); 56 int u = i; 57 ans.push_back(u); 58 while(to[u]) { 59 u = to[u]; 60 ans.push_back(u); 61 } 62 for(int j = 0; j < ans.size(); j++) printf("%d%c", ans[j], j == ans.size() - 1 ? '\n' : ' '); 63 } 64 printf("%d\n", n - result); 65 } 66 int main() 67 { 68 scanf("%d%d", &n, &m); 69 init(); 70 work(); 71 return 0; 72 }
方法二:网络最大流。这里的做法和二分图匹配用最大流的做法是一样的。附加炒鸡源S和炒鸡汇T,然后建图(边权为1),最后跑最大流,输出时方法很多,我选择的是从汇点按残余流量的有无来往前找一条路径并递归输出。
代码(手打Dinic):
1 #include<bits/stdc++.h> 2 #define il inline 3 using namespace std; 4 const int N=1000,inf=23333333; 5 int n,m,s,t=520,h[N],dis[N],cnt=1,fa[N]; 6 struct edge{ 7 int to,net,v; 8 }e[100005]; 9 il void add(int u,int v,int w) 10 { 11 e[++cnt].to=v,e[cnt].net=h[u],e[cnt].v=w,h[u]=cnt; 12 e[++cnt].to=u,e[cnt].net=h[v],e[cnt].v=0,h[v]=cnt; 13 } 14 queue<int>q; 15 il bool bfs() 16 { 17 memset(dis,-1,sizeof(dis)); 18 q.push(s),dis[s]=0; 19 while(!q.empty()) 20 { 21 int u=q.front();q.pop(); 22 for(int i=h[u];i;i=e[i].net) 23 if(dis[e[i].to]==-1&&e[i].v>0)dis[e[i].to]=dis[u]+1,q.push(e[i].to); 24 } 25 return dis[t]!=-1; 26 } 27 il int dfs(int u,int op) 28 { 29 if(u==t)return op; 30 int flow=0,used=0; 31 for(int i=h[u];i;i=e[i].net) 32 { 33 int v=e[i].to; 34 if(dis[v]==dis[u]+1&&e[i].v) 35 { 36 used=dfs(v,min(op,e[i].v)); 37 if(!used)continue; 38 flow+=used,op-=used; 39 e[i].v-=used,e[i^1].v+=used; 40 fa[u]=v; 41 if(!op)break; 42 } 43 } 44 if(!flow)dis[u]=-1; 45 return flow; 46 } 47 il void print(int x) 48 { 49 if(x<=s)return; 50 printf("%d ",x); 51 for(int i=h[x];i;i=e[i].net) 52 if(!e[i].v&&e[i].to<=n*2)print(e[i].to-n); 53 } 54 int main() 55 { 56 scanf("%d%d",&n,&m); 57 for(int i=1;i<=n;i++)fa[i]=i; 58 for(int i=1;i<=n;i++)add(s,i,1),add(i+n,t,1); 59 int u,v; 60 for(int i=1;i<=m;i++) 61 { 62 scanf("%d%d",&u,&v); 63 add(u,v+n,1); 64 } 65 int ans=n; 66 while(bfs())ans-=dfs(s,inf); 67 for(int i=h[t];i;i=e[i].net) 68 { 69 if(e[i].v)continue; 70 print(e[i].to-n),printf("\n"); 71 } 72 printf("%d",ans); 73 return 0; 74 }