1. 程式人生 > >有向無環圖(DAG)的最小路徑覆蓋

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

定理: 柯尼希定理:二分圖最小點覆蓋的點數=最大匹配數。
最小路徑覆蓋的邊數=頂點數n-最大匹配數
最大獨立集=最小路徑覆蓋=頂點數n-最大匹配數

增廣路定理:用未蓋點表示不與任何匹配邊鄰接的點,其他點位匹配點,即恰好和一條匹配邊臨界的點。從未蓋點出發,依次經過非匹配邊,匹配邊,非匹配邊,匹配邊。。。所得到的路徑稱為交替路。注意,如果交替路的終點時一個未蓋點,則稱這條交替路位一條增廣路。在增廣路中,非匹配邊比匹配邊多一條。增廣路的作用是改進匹配。如果有一條增廣路,那麼把此路上的匹配邊和非匹配邊互換,得到的匹配比剛才多一邊。反過來,如果找不到增廣路,則當前匹配就是最大匹配。

查詢增廣路,存在增廣路就交換增廣路上的非匹配邊和匹配邊,這樣會使得當前最大匹配數+1。

DAG的最小路徑覆蓋

定義:在一個有向圖中,找出最少的路徑,使得這些路徑經過了所有的點。

最小路徑覆蓋分為最小不相交路徑覆蓋最小可相交路徑覆蓋

最小不相交路徑覆蓋:每一條路徑經過的頂點各不相同。如圖,其最小路徑覆蓋數為3。即1->3>4,2,5。

最小可相交路徑覆蓋:每一條路徑經過的頂點可以相同。如果其最小路徑覆蓋數為2。即1->3->4,2->3>5。

特別的,每個點自己也可以稱為是路徑覆蓋,只不過路徑的長度是0。

DAG的最小不相交路徑覆蓋

演算法:把原圖的每個點V拆成VxVxVyVy兩個點,如果有一條有向邊A->B,那麼就加邊A

x>ByAx−>By。這樣就得到了一個二分圖。那麼最小路徑覆蓋=原圖的結點數-新圖的最大匹配數。

證明:一開始每個點都是獨立的為一條路徑,總共有n條不相交路徑。我們每次在二分圖裡找一條匹配邊就相當於把兩條路徑合成了一條路徑,也就相當於路徑數減少了1。所以找到了幾條匹配邊,路徑數就減少了多少。所以有最小路徑覆蓋=原圖的結點數-新圖的最大匹配數。

因為路徑之間不能有公共點,所以加的邊之間也不能有公共點,這就是匹配的定義。

//
//  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,不必經過中點的,那麼就轉化成了最小不相交路徑覆蓋。

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

最小路徑覆蓋問題值得注意的問題

首先,最小路徑覆蓋=總節點數-最大匹配數。這個應該已經是路人皆知了。

所謂最小路徑覆蓋,是指在一個有向圖中,找出最少的幾條路徑,用它們來覆蓋全圖

這裡說的值得注意的地方,如果有向圖的邊有相交的情況,那麼就不能簡單的對原圖求二分匹配了

舉個例子,假設有圖:1->2    2->5     2->3      4->2,事實上,這其實就是兩條邊:1->5 4->3 ,節點2只是他們的一個交點


如果只是簡單的在原圖的基礎上求二分匹配,那麼得到的匹配答案是2,最小路徑覆蓋答案便是5-2=3。

可是隨便一看都能看看出端倪,這個圖中,只需要兩個點便可以探索完整個地圖,這裡最小路徑覆蓋數明顯是2。

問題究竟出在哪裡呢?其實就和這個交點2有關。既然邊有相交,那麼他們的連通性也應該連通下去。

解決的辦法是對原圖進行一次閉包傳遞(也就是flody),於是便增加了四條邊:1->3  1->5   4->3  4->5

這時再求最大匹配數,匹配答案便是3,最小路徑覆蓋值為2,這是正確答案!

相關推薦

構建DAG模型解決矩形巢狀問題 以nyoj16為例

 DAG(Directed Acyclic Graph):在圖論中,如果一個有向圖無法從某個頂點出發經過若干條邊回到該點,則這個圖是一個有向無環圖(DAG圖)。有向無環圖上的動態規劃是學習動態規劃的基礎。很多問題都可以轉化為DAG上的最長路和最短路或計數問題。 分析

DAG技術:超越區塊鏈的分散式賬本

一、起源 DAG(Directed Acyclic Graph,有向無環圖)是一種資料結構,最早提出在區塊鏈中加入DAG概念作為演算法,是在2013年的bitcointalk論壇,被稱作為“Ghost協議”,這一提議也是為了解決當時比特幣的擴容問題。後來,在NXT社群,

DAG路徑覆蓋

定理: 柯尼希定理:二分圖最小點覆蓋的點數=最大匹配數。 最小路徑覆蓋的邊數=頂點數n-最大匹配數 最大獨立集=最小路徑覆蓋=頂點數n-最大匹配數 增廣路定理:用未蓋點表示不與任何匹配邊鄰

POJ3249 工作難題DAG的單源路徑

題意: 有N個城市,它們之間存在一些單向路徑,若可以從城市i到城市j,則一定無法從城市j到達城市i,只出不入的城市稱為源點城市,只入不出的城市稱為終點城市。已知到達某個城市就可以獲得或者失去一定的錢財。現在狗先生從某個源點出發,到達某個終點停止,他想擁有儘量多的錢財。要求給

NOIP模擬 玄學建+短路

內網傳送門 【題目分析】 SPFA竟然有人亂搞A了?orz(蒟蒻亂搞只有40pts qwq) 很巧妙的建圖思路,將每條路徑視為一個點,從一條路徑i到達另一條路徑j,如果w[i]<w[j],那麼會產生w[j]-w[i]的費用,否則不會產生任何費用。 所以考慮將所有

資料結構——AOV網、AOE網

有向無環圖是一個無環的有向圖, 是描述一項工程或系統的進行過程的有效工具。幾乎所有的工程都可分為若干個稱做活動的子工程。 有兩種常用的活動網路        1. AOV網(Activity On Vertices)——用頂點表示活動的網路  

DAG上的常見推論

1.DAG上某條邊可能被經過的次數數量: 通過在DAG上的總結,再結合我們在小學學過的乘法原理,我們可以考慮到一個規律:在一條邊M(u->v)上,通過M的方法數量為從源點到達u的方式數量*從終點

DAG技術

在區塊鏈領域從來不缺少專業的技術詞彙,比如非對稱加密技術、分片技術、DAG技術……可以這麼說,不懂點技術,連白皮書都看不懂。今天,將為大家介紹一種新的區塊鏈技術——DAG技術 一、產生的原因 我們都知道底層公鏈是區塊鏈技術落地的基礎,只有公鏈技術成熟,區塊鏈應用才能走進千家萬戶。但長久

HDU 3249 Test for job 上的長路,DP

code head struct sin == cout article scanf for ?? 解題思路: 求有向無環圖上的最長路。簡單的動態規劃#include <iostream> #include <cstring> #include

深度優先演算法DFS遍歷計算路徑

遍歷有向無環圖,尋找最優路徑: 1、假設我們從A點走到B點,可以經過不同的地方,分別用1,2,3,4,5,6表示,A用0表示,B用7表示,從一個地方到另一個地方,中間的路好走的程度用w表示,w越大表示越好走,因此我們可以建立數學模型如下圖1所示: 圖1 2、根據數學模

拓撲排序判斷是否是

要進行拓撲排序之前,該圖要是有向無環圖。 排序方法: 1、從有向圖中選取一個沒有前驅的頂點,並輸出之 ;2、從有向圖中刪去此頂點以及所有以它為尾的弧; 3、重複上述兩步,直至圖空,或者圖不空但找不到無前驅的頂點為止。 #include<stdio.h>

短路模板——用拓撲排序解決中的短路

測試資料: 8 13 5 4 0.35 4 7 0.37 5 7 0.28 5 1 0.32 4 0 0.38 0 2 0.26 3 7 0.39 1 3 0.29 7 2 0.34 6 2 0.40 3 6 0.52 6 0 0.58 6 4 0.93 測試結果: 5

的拓撲排序DFS實現

1.有向無環圖的拓撲排序 // enDegree表示每個頂點的入度,這個資料結構可以從圖的結構求出來 // graph是一個二維陣列,但是這個陣列不是圖的鄰接矩陣,graph[i][j]表示依賴於i的第

DAG 拓撲排序 程式碼解釋

目錄: DAG定義 舉例描述 實際運用 演算法描述 演算法實戰 演算法視覺化 定義 在圖論中,由一個有向無環圖的頂點組成的序列,當且僅當滿足下列條件時,稱為該圖的一個拓撲排序(英語:Topological sorting)。 每個頂點出現且只出現一

SparkDAG圖解與演示

目錄:1、有向無環圖 2、程式碼結構 3、程式碼學習步鄹及方法 4、重點程式碼講解 5、程式碼展現 6、執行結果 ——————————————————————————————————— 1、有向無環圖 在圖論中,如果一個有向圖無法從某個頂點出發經過若干條邊回到該點,則這個

Spark UI控制檯瀏覽器介面不顯示DAG問題原因

使用Spark UI控制檯檢視Spark執行過程時,始終不顯示DAG圖,-_-||  如下: 後來發現,是因為360瀏覽器選擇了相容模式,導致不能正常顯示DAG圖,修改為極速模式後顯示正常,遇到

演算法: (DAG)的拓撲排序

更新: 拓撲排序有2中方法(最後結果可能不同,因為拓撲排序有多解)。 一個簡單的求拓撲排序的演算法是先找出任意一個沒有入邊的頂點,然後將它和它的邊從圖中刪除。然後對剩餘部分使用同樣的操作。 public ArrayList<Integer&g

(DAG)的所有拓撲序列

如果index>length(S(i)),即index的值如果大於集合S(i)的大小了,說明當前集合S(i)中的值都用過了,必須回溯到前一個序列元素result[i-1],將它的值變成它對應集合(S(i-1))中的下一個可能的值.假設之前result[i-1]使用的是S(i-1)[n],那麼現在resu

拓撲排序+(DAG)的檢測

參考: 演算法導論第三版 p356, 資料結構與演算法分析p218, 演算法入門經典p110 拓撲排序的兩張方法: 1.dfs搜尋 2.模擬人工的拓撲 兩種方法的效率都是O(V + E)$$拓撲排序的方法也是有向無環圖檢測的方法 /* 拓撲排序 dfs搜尋 - 鄰接表 可

C#實現(DAG)拓撲排序

對一個有向無環圖(Directed Acyclic Graph簡稱DAG)G進行拓撲排序,是將G中所有頂點排成一個線性序列,使得圖中任意一對頂點u和v,若邊(u,v)∈E(G),則u線上性序列中出現在v之前。通常,這樣的線性序列稱為滿足拓撲次序(Topological Order)的序列,簡稱拓撲序列。簡單的