hihocoder1394網路流三之最小路徑覆蓋
題目連線:
一定要見雙向邊,第二次了…………………………。因為這兩邊的點是一樣的
題目:
描述
國慶期間正是旅遊和遊玩的高峰期。
小Hi和小Ho的學習小組為了研究課題,決定趁此機會派出若干個調查團去沿途檢視一下H市內各個景點的遊客情況。
H市一共有N個旅遊景點(編號1..N),由M條單向遊覽路線連線。在一個景點遊覽完後,可以順著遊覽線路前往下一個景點。
為了避免遊客重複遊覽同一個景點,遊覽線路保證是沒有環路的。
每一個調查團可以從任意一個景點出發,沿著計劃好的遊覽線路依次調查,到達終點後再返回。每個景點只會有一個調查團經過,不會重複調查。
舉個例子:
上圖中一共派出了3個調查團:
1. 藍色:調查景點;2
2. 橙色:調查景點;1->3->4->6
3. 綠色:調查景點;5->7
當然對於這個圖還有其他的規劃方式,但是最少也需要3個調查團。
由於小組內的人數有限,所以大家希望調查團的數量儘可能少,同時也要將所有的景點都進行調查。
當然,如何規劃調查團線路的任務落到了小Hi和小Ho的頭上。
輸入
第1行:2個整數N,M。1≤N≤500,0≤M≤20,000。
第2..M+1行:2個數字u,v,表示一條有向邊(u,v)。保證不會出現重複的邊,且不存在環。
輸出
第1行:1個整數,表示最少需要的調查團數量。
樣例輸入7 7 1 2 1 3 2 4 3 4 4 5 4 6 5 7樣例輸出
3
解法:
小Ho:所以這一次我們應該如何來解決這個問題呢?
小Hi:嗯,這次的問題被稱為最小路徑覆蓋。給定一個有向無環圖,用最少的路徑數量去保證所有點都被覆蓋住。
小Ho:既然有名字,那一定有固定的解法了?
小Hi:沒錯,最小路徑覆蓋的結果等於N-最大二分匹配。
小Ho:二分匹配?這和二分匹配有什麼關係?給定的有向圖不一定是二分圖吧。
小Hi:當然不是在原圖上進行的二分匹配了。我們需要對原圖進行轉化,同時這一次我們還要學習如何用網路流去解決二分匹配的問題。
小Ho:好,你快給我講講。
小Hi:好的好的,你別急。我們先從例子來分析:
在這個例子中,我們選擇的三條路徑都被染上了顏色。你有發現什麼特殊之處麼?
小Ho:嗯...<小Ho思考了一小會兒>...並沒有什麼特別的地方啊?
小Hi:把黑色的邊去掉,你再看看呢?主要注意的是每個點的出入度數量。
小Ho:對於一條路徑,起點的入度為0,終點的出度為0,中間節點的出入度都為1。但這不是路徑都應該具有的性質麼?
小Hi:這個性質就是我們解決題目的關鍵!
每一個點最多隻能有1個後繼,同時每一個點最多隻能有1個前驅。
假如我們選擇了一條邊(u,v),也就等價於把前驅u和後繼v匹配上了。這樣前驅u和後繼v就不能和其他節點匹配。
小Ho:那就是一個前驅匹配一個後繼?
小Hi:是的,利用這個我們可以這樣來構圖:
將每一個點拆分成2個,分別表示它作為前驅節點和後繼節點。將所有的前驅節點作為A部,所有後繼節點作為B部。
接下來進行連邊,若原圖中存在一條邊(u,v),則連線A部的u和B部的v。
那麼小Ho,你在這個上面做一個最大二分匹配怎麼樣?
小Ho:好!......完成了。
小Hi:不錯,讓我再把對應的顏色染出來:
其中實線表示被選中的匹配,虛線表示未被選中的。
有沒有發現,和原圖剛好有著對應的關係。未被選中的匹配也正好對應了原圖中我們沒有選擇的黑色邊。
小Ho:是的呢?這是為什麼呢?
小Hi:其實原理很簡單。我們進行的匹配是前驅和後繼的匹配。假如存在選中的匹配(i,j)和(j,k)。則表示原圖中存在一條路徑(i,j,k)。
比如例子中的匹配(1,3),(3,4),(4,6)就對應了原圖中的路徑(1,3,4,6)。
這樣在匹配結束的時候,我們就可以直接通過匹配的情況來確定選中的路徑。
小Ho:這個我懂了,但是如何保證這樣就能得到最小的路徑覆蓋呢?
小Hi:你想想,每一條路徑起點有什麼特殊的地方?
小Ho:路徑的起點入度為0...哦!我知道了。
如果一個點是路徑起點的話,它在B部的節點一定是沒有匹配上的。
經過最大匹配演算法後,B部剩下沒有被匹配的點一定是最少的,也就對應了最小需要的路徑數。
所以最小路徑覆蓋的結果才是N-最大匹配數。
小Hi:正是這樣,這樣問題也就解決了。接下來第二個問題,怎麼用網路流來解決二分匹配呢?
小Ho:上一次我們講了二分多重匹配,二分匹配不就是它的簡化版麼。
只需要把源點s到A部的邊和B部到匯點t的邊容量限定為1就可以了!
小Hi:嗯,那麼就只差最後一步了。
小Ho:這我也知道!實現嘛,交給我
程式碼:
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <cmath>
#include <stack>
#include <queue>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#include <stdlib.h>
#include <iomanip>
#include <fstream>
using namespace std;
#pragma comment(linker, "/STACK:102400000,102400000")
#define maxn 1010
#define MOD 1000000007
#define INF 0x3f3f3f3f
#define mem(a , b) memset(a , b , sizeof(a))
#define LL long long
#define ULL unsigned long long
#define FOR(i , n) for(int i = 1 ; i<= n ; i ++)
typedef pair<int , int> pii;
int n , m , ed;
vector<int>v[maxn];
int vis[maxn] , path[maxn] , a[maxn][maxn];
int flow[maxn] , ans;
bool BFS()
{
mem(vis , 0);
mem(path , -1);
mem(flow , 0);
queue<int>q;
while(!q.empty()) q.pop();
q.push(0);
vis[0] = 1;
flow[0] = INF;
while(!q.empty())
{
int cur = q.front();
q.pop();
if(cur == ed) return 1;
for(int i = 0 ; i < v[cur].size() ; i ++)
{
if(!vis[v[cur][i]] && a[cur][v[cur][i]] > 0)
{
vis[v[cur][i]] = 1;
flow[v[cur][i]] = min(flow[cur] , a[cur][v[cur][i]]);
path[v[cur][i]] = cur;
q.push(v[cur][i]);
}
}
}
return 0;
}
int main()
{
while(scanf("%d %d" , &n , &m) != EOF)
{
int s , to;
mem(a , 0);
ed = n + n + 1;
for(int i = 0 ; i < m ; i ++)
{
scanf("%d %d" , &s , &to);
v[s].push_back(to + n);
v[to+n].push_back(s);
a[s][to+n] = 1;
}
for(int i = 1 ; i <= n ; i ++)
{
v[0].push_back(i);
v[i].push_back(0);
a[0][i] = 1;
v[i+n].push_back(ed);
v[ed].push_back(i+n);
a[i+n][ed] = 1;
}
ans = 0;
while(BFS())
{
ans += flow[ed];
int fir = ed , sec = path[fir];
while(sec != -1)
{
a[fir][sec] += flow[ed];
a[sec][fir] -= flow[ed];
fir = sec;
sec = path[fir];
}
}
printf("%d\n" , n - ans);
}
return 0;
}