网络流24题3. 最小路径覆盖问题

最小路径覆盖问题

Description

给定有向图 G=(V,E)。设 P 是 G 的一个简单路(顶点不相交)的集合。如果 V 中每个顶点恰好在 P 的一条路上,则称 P 是 G 的一个路径覆盖。P 中路径可以从 V 的任何一个顶点开始,长度也是任意的,特别地,可以为 0。G 的最小路径覆盖是 G 的所含路径条数最少
的路径覆盖。
设计一个有效算法求一个有向无环图 G 的最小路径覆盖。
对于给定的给定有向无环图 G,编程找出 G 的一个最小路径覆盖。

Input

文件第 1 行有 2 个正整数 n 和 m。n 是给定有向无环图 G 的顶点数,m是 G 的边数。接下来的 m行,每行有 2 个正整数 i 和 j,表示一条有向边(i,j)。

Output

从第 1 行开始,每行输出一条路径。文件的最后一行是最少路径数。

题目大意

有向无环图最小路径覆盖。

题解

【建模方法】

构造二分图,把原图每个顶点i拆分成二分图X,Y集合中的两个顶点Xi和Yi。对于原图中存在的每条边(i,j),在二分图中连接边(Xi,Yj)。然后把二分图最大匹配模型转化为网络流模型,求网络最大流。

最小路径覆盖的条数,就是原图顶点数,减去二分图最大匹配数。沿着匹配边查找,就是一个路径上的点,输出所有路径即可。

【建模分析】

对于一个路径覆盖,有如下性质:

1、每个顶点属于且只属于一个路径。
2、路径上除终点外,从每个顶点出发只有一条边指向路径上的另一顶点。

所以我们可以把每个顶点理解成两个顶点,一个是出发,一个是目标,建立二分图模型。该二分图的任何一个匹配方案,都对应了一个路径覆盖方案。如果匹配数为0,那么显然路径数=顶点数。每增加一条匹配边,那么路径覆盖数就减少一个,所以路径数=顶点数 - 匹配数。要想使路径数最少,则应最大化匹配数,所以要求二分图的最大匹配。

注意,此建模方法求最小路径覆盖仅适用于有向无环图,如果有环或是无向图,那么有可能求出的一些环覆盖,而不是路径覆盖。

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;

const int N = 1000 + 10, M = 50000 + 10, inf = 0x3f3f3f3f;

struct Edge{
    int fr, to, cap, flow;
}edg[M];
int hd[N], nxt[M];
int d[N], vis[N], q[N], dfn;
int s, t;
int n, m, ans, tot;

inline void in(int &x){
    x = 0; char c; c = getchar();
    while(c < '0' || c > '9') c = getchar();
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0'; c = getchar();}
}
void insert(int u, int v, int w){
    edg[tot].fr = u, edg[tot].to = v, edg[tot].cap = w;
    nxt[tot] = hd[u], hd[u] = tot;
    tot++;
    edg[tot].fr = v, edg[tot].to = u;
    nxt[tot] = hd[v], hd[v] = tot;
    tot++;
}
void init(){
    memset(hd, -1, sizeof(hd));
    in(n); in(m);
    s = 0, t = n * 2 + 1;
    int u, v;
    for(int i = 1; i <= m; i++){
        in(u); in(v);
        insert(u, n + v, 1);
    }
    for(int i = 1; i <= n; i++){
        insert(s, i, 1);
        insert(i + n, t, 1);
    }
    /*for(int i = 0; i < tot; i++)
        printf("%d %d %d %d\n", edg[i].fr, edg[i].to, edg[i].cap, edg[i].flow);*/
}
bool bfs(){
    int head = 1, tail = 1;
    q[1] = s; vis[s] = ++dfn; d[s] = 0;
    while(head <= tail){
        int u = q[head++];
        for(int i = hd[u]; i >= 0; i = nxt[i]){
            Edge &e = edg[i];
            if(vis[e.to] == dfn || e.cap <= e.flow) continue;
            vis[e.to] = dfn;
            d[e.to] = d[u] + 1;
            q[++tail] = e.to;
        }
    }
    return vis[t] == dfn;
}
int dfs(int x, int a){
    if(x == t || a == 0) return a;
    int flow = 0, f;
    for(int i = hd[x]; i >= 0; i = nxt[i]){
        Edge &e = edg[i];
        if(d[e.to] == d[x] + 1 && (f = dfs(e.to, min(a, e.cap - e.flow))) > 0){
            flow += f;
            e.flow += f;
            edg[i^1].flow -= f;
            a -= f;
            if(a == 0) break;
        }
    }
    return flow;
}
void work(){
    while(bfs())
        ans += dfs(s, inf);
    //printf("%d\n", ans);
    printf("%d\n", n - ans);
}
int main(){
    freopen("prog83.in", "r", stdin);
    freopen("prog83.out", "w", stdout);
    init();
    work();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值