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: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
輸出樣例#1: 1 4 7 10 11
2 5 8
3 6 9
3
說明
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(!op)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 }
P2764 最小路徑覆蓋問題(網絡流24題之一)