网络流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;
}
posted @ 2021-08-16 17:06  Paranoid5  阅读(28)  评论(0编辑  收藏  举报