DAG的最小路徑覆蓋
定義:在一個有向圖中,找出最少的路徑,使得這些路徑經過了所有的點。
最小路徑覆蓋分為最小不相交路徑覆蓋和最小可相交路徑覆蓋。
最小不相交路徑覆蓋:每一條路徑經過的頂點各不相同。如圖,其最小路徑覆蓋數為3。即1->3>4,2,5。
最小可相交路徑覆蓋:每一條路徑經過的頂點可以相同。如果其最小路徑覆蓋數為2。即1->3->4,2->3>5。
特別的,每個點自己也可以稱為是路徑覆蓋,只不過路徑的長度是0。
DAG的最小不相交路徑覆蓋
算法:把原圖的每個點V拆成$V_x$和$V_y$兩個點,如果有一條有向邊A->B,那么就加邊$A_x->B_y$。這樣就得到了一個二分圖。那么最小路徑覆蓋=原圖的結點數-新圖的最大匹配數。
證明:一開始每個點都是獨立的為一條路徑,總共有n條不相交路徑。我們每次在二分圖里找一條匹配邊就相當於把兩條路徑合成了一條路徑,也就相當於路徑數減少了1。所以找到了幾條匹配邊,路徑數就減少了多少。所以有最小路徑覆蓋=原圖的結點數-新圖的最大匹配數。
因為路徑之間不能有公共點,所以加的邊之間也不能有公共點,這就是匹配的定義。
習題:POJ1422
// // main.cpp // POJ1422最小不想交路徑覆蓋 // // Created by beMaster on 16/4/8. // Copyright © 2016年 beMaster. All rights reserved. // #include <iostream> #include <stdio.h> #include <string.h> #include <vector> using namespace std; const int N = 200 + 10; vector<int> g[N]; int cy[N]; bool vis[N]; bool dfs(int u){ for(int i=0; i<g[u].size(); ++i){ int v = g[u][i]; if(vis[v]) continue; vis[v] = true; if(cy[v]==-1 || dfs(cy[v])){ cy[v] = u; return true; } } return false; } int solve(int n){ int ret = 0; memset(cy, -1, sizeof(cy)); for(int i=1;i<=n;++i){ memset(vis, 0, sizeof(vis)); ret += dfs(i); } return n - ret; } int main(int argc, const char * argv[]) { int t,n,m; int u,v; scanf("%d",&t); while(t--){ scanf("%d%d",&n,&m); for(int i=1;i<=n;++i) g[i].clear(); for(int i=0;i<m;++i){ scanf("%d%d",&u,&v); g[u].push_back(v); } int ans = solve(n); printf("%d\n",ans); } return 0; }
DAG的最小可相交路徑覆蓋
算法:先用floyd求出原圖的傳遞閉包,即如果a到b有路徑,那么就加邊a->b。然后就轉化成了最小不相交路徑覆蓋問題。
證明:為了連通兩個點,某條路徑可能經過其它路徑的中間點。比如1->3->4,2->4->5。但是如果兩個點a和b是連通的,只不過中間需要經過其它的點,那么可以在這兩個點之間加邊,那么a就可以直達b,不必經過中點的,那么就轉化成了最小不相交路徑覆蓋。
題目:POJ2594
// // main.cpp // POJ2594最小可相交路徑覆蓋 // // Created by beMaster on 16/4/8. // Copyright © 2016年 beMaster. All rights reserved. // #include <iostream> #include <stdio.h> #include <string.h> #include <vector> using namespace std; const int N = 500 + 10; bool dis[N][N]; bool vis[N]; int cy[N]; void floyd(int n){ for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) for(int k=1;k<=n;++k) if(dis[i][k] && dis[k][j])//傳遞可達性 dis[i][j] = true; } bool dfs(int u, int n){ for(int i=1;i<=n;++i){ if(!vis[i] && dis[u][i]){ vis[i] = true; if(cy[i]==-1 || dfs(cy[i], n)){ cy[i] = u; return true; } } } return false; } int solve(int n){ int cnt = 0; memset(cy,-1,sizeof(cy)); for(int i=1;i<=n;++i){ memset(vis,0,sizeof(vis)); cnt += dfs(i, n); } return n - cnt; } int main(int argc, const char * argv[]) { int n,m; int a,b; while(scanf("%d%d",&n,&m),n+m){ for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) dis[i][j] = false; for(int i=1;i<=m;++i){ scanf("%d%d",&a,&b); dis[a][b] = true; } floyd(n); int ans = solve(n); printf("%d\n",ans); } return 0; }
參考: