网络流24题(三)
网络流24题(3)
三、最小路径覆盖问题
题目描述
给定有向图 \(G=(V,E)\) 。设 \(P\) 是 \(G\) 的一个简单路(顶点不相交)的集合。如果 \(V\) 中每个定点恰好在\(P\)的一条路上,则称 \(P\) 是 \(G\) 的一个路径覆盖。\(P\)中路径可以从 \(V\) 的任何一个定点开始,长度也是任意的,特别地,可以为 \(0\) 。\(G\) 的最小路径覆盖是 \(G\) 所含路径条数最少的路径覆盖。设计一个有效算法求一个 \(DAG\) (有向无环图) \(G\) 的最小路径覆盖。
输入格式
第一行有 \(2\) 个正整数 \(n\) 和 \(m\) 。 \(n\) 是给定\(DAG\)(有向无环图) \(G\) 的顶点数,\(m\) 是 \(G\) 的边数。接下来的 \(m\) 行,每行有两个正整数 \(i\) 和 \(j\) 表示一条有向边 \((i,j)\)。
输出格式
从第\(1\) 行开始,每行输出一条路径。文件的最后一行是最少路径数。
题解
模型:
\(DAG\)最小路径覆盖,转化为二分图最大匹配。
建图与实现:
构造二分图,把原图每个顶点\(i\)拆成两个顶点j集合\(X\),\(Y\),对于原图中的每一条边,\(X\)和\(Y\)之间就形成一条边。
求二分图最大匹配,用顶点个数\(n\)-最大匹配数。
对于路径输出:如果一个顶点\(i\)有匹配边,用一个\(vis\)数组表示有前驱节点,就使用一个数组\(post[i]\)记录后继节点,初始值\(-1\)表示没有后继结点。
以\(vis\)为\(false\)的节点为起点,遍历后继结点。
分析
对于每一个顶点,属于且只属于一条路径。
如果要想覆盖\(DAG\)上的所有顶点,最多需要多少条路径?
\(n\)条,只要每一条路经只有一个顶点就行了。
我们可以选择其中若干条路径合并,最多的合并数就对应着最少的覆盖数。
来看看合并路径的过程:
我们选择其中的两条路径进行合并,
路径1:\(u_1\)->\(v_1\),路径2:\(u_2\)->\(v_2\),此时有一条有向边\((v_1,u_2)\),我们可以根据这条边进行一次合并。
合并的过程是将一个起点\(v_1\)和一个终点 \(u_2\)连接。
这时候我们注意两个关键字:起点,终点,我们每一次关注的都是起点和终点,所以我们 这样做,在原图的基础上把每一个点都拆成起点和终点此时形成一张二分图,每次进行的路径合并,就是在二分图上添加一条边,因为每一个点只会在一条路径中出现一次,那么在二分图中每一个点也只会有一条边。不难发现,这就是匹配。
也就是最大匹配对应了一个最小路径覆盖。
但是要注意,如果图有环,那么推理将不再成立。因为此时我们会求出环覆盖。
```cpp
#include <bits/stdc++.h>
using namespace std;
#define ll int
const ll N = 305,M = 2e5+5,inf = 0x3f3f3f3f;
ll head[N],cnt = 1;
struct Edge{ll to,w,nxt;}edge[M];
void add(ll u,ll v,ll w){
edge[++cnt].to = v;
edge[cnt].w = w;
edge[cnt].nxt = head[u];
head[u] = cnt;
}
ll n,m,s,t,lv[N],cur[N];
ll f[2*N+50];
bool vis[M] = {false};
bool bfs(){
memset(lv, -1, sizeof(lv));
lv[s] = 0;
memcpy(cur, head, sizeof(head));
queue<int> q;q.push(s);
while (!q.empty()){
int p = q.front();q.pop();
for (int eg = head[p]; eg; eg = edge[eg].nxt){
int to = edge[eg].to, vol = edge[eg].w;
if (vol > 0 && lv[to] == -1)lv[to] = lv[p] + 1, q.push(to);
}
}
return lv[t] != -1;
}
int dfs(int p = s, int flow = inf){
if (p == t)return flow;
int rmn = flow;
for (int &eg = cur[p]; eg; eg = edge[eg].nxt){
if (!rmn)break;
int to = edge[eg].to, vol = edge[eg].w;
if (vol > 0 && lv[to] == lv[p] + 1){
int c = dfs(to, min(vol, rmn));
rmn -= c;
edge[eg].w -= c;
edge[eg ^ 1].w += c;
}
}
return flow - rmn;
}
ll dinic(){
ll ans = 0;
while(bfs()) ans += dfs();
return ans;
}
void print(){
memset(f,-1,sizeof f);
for(ll eg = head[0];eg;eg = edge[eg].nxt){
if(edge[eg].w == 0){
ll v = edge[eg].to;
for(ll j = head[v];j;j = edge[j].nxt){
if(edge[j].w == 0){
f[v] = edge[j].to-n;
vis[edge[j].to-n] = true;
}
}
}
}
for(ll i = 1;i <= n;i++){
if(!vis[i]){
ll x = i;
cout<<x;
while(f[x]!=-1){
cout<<' '<<f[x];
x = f[x];
}
cout<<endl;
}
}
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;
s = 0,t = 2*n+1;
while(m--){
ll u,v;cin>>u>>v;
add(u,v+n,1);
add(v+n,u,0);
}
for(ll i = 1;i <= n;i++){
add(s,i,1);add(i,s,0);
add(i+n,t,1);add(t,i+n,0);
}
ll ans = dinic();
print();
cout<<n-ans<<endl;
return 0;
}