有向無環圖(DAG)的最小路徑覆蓋


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;
}
 

 

 

參考

二分圖大講堂——徹底搞定最大匹配數(最小覆蓋數)、最大獨立數、最小路徑覆蓋、帶權最優匹配


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM