线性规划与网络流 03最小路径覆盖问题

线性规划与网络流 03最小路径覆盖问题

题意:

有向无环图,如何以最少的路径数覆盖图中所有点,要求每个点在且仅在一条路径上。

思路:

定理:一个有向无环图的最小路径覆盖等于最大匹配。

也就是说,求出最大匹配后,用总点数减去最大匹配值即能得到最小路径数。

 

如何用网络流跑二分匹配?把原来的点拆成两个点,命名为xy

{123...n}->{x1,x2,x3,...xn}U{y1,y2,y3,...yn}

然后设源点S,向每个x连容量为1的边。

设汇点T,每个yT连容量为1的边。

若某个原始点ij有有向边(ij),则转化成(xi,yj)。

然后跑一遍最大流,输出方案时dfs一下即可,详见程序。

源码:

#include <cstdio>

#include <cstring>

#include <cstdlib>

#include <cmath>

#include <algorithm>

#include <iostream>

#include <queue>

#include <vector>

using namespace std;

#define inf (1000000000)

const int MAXN = 1000 + 5;

int head[MAXN], cur[MAXN], cnt;

int n, m;

int d[MAXN], p[MAXN], num[MAXN];

queue<int>que;

struct Edge

{

    int u, v;

    int flow, ne;

    Edge(){}

    Edge(int _u, int _v, int _flow){u = _u, v = _v, flow = _flow, ne = head[_u];}

}edge[MAXN * MAXN * 2];

void add_edge(int u, int v)

{

    edge[cnt] = Edge(u, v, 1);

    head[u] = cnt++;

    edge[cnt] = Edge(v, u, 0);

    head[v] = cnt++;

}

void init()

{

    cnt = 0;

    memset(head, -1, sizeof(head));

    int u, v;

    for(int i = 1 ; i <= n ; i++)

        add_edge(0, i * 2), add_edge(i * 2 + 1, 1);

    for(int i = 0 ; i < m ; i++){

        scanf("%d%d", &u, &v);

        add_edge(u * 2, v * 2 + 1);

    }

}

int vis[MAXN];

void BFS(int t)

{

    memset(vis, 0, sizeof(vis));

    while(!que.empty()) que.pop();

    d[t] = 0;

    vis[t] = 1;

    que.push(t);

    while(!que.empty()){

        int u = que.front();    que.pop();

        for(int now = head[u] ; now != -1 ; now = edge[now].ne){

            int v = edge[now].v;

            if(vis[v] == 0){

                vis[v] = 1;

                que.push(v);

                d[v] = d[u] + 1;

            }

        }

    }

}

int Augment(int s, int t)

{

    int ans = inf;

    int now = t;

    while(now != s){

        ans = min(ans, edge[p[now]].flow);

        now = edge[p[now]].u;

    }

    now = t;

    while(now != s){

        edge[p[now]].flow -= ans;

        edge[p[now]^1].flow += ans;

        now = edge[p[now]].u;

    }

    return ans;

}

int ISAP(int s, int t)

{

    int flow = 0;

    BFS(t);

    memset(num, 0, sizeof(num));

    for(int i = 0 ; i <= n * 2 ; i++)

        cur[i] = head[i], num[d[i]]++;

    while(!que.empty()) que.pop();

    int u = s;

    while(d[s] < n * 2 + 1){

        if(u == t){

            flow += Augment(s, t);

            u = s;

        }

        int ok = 0;

        for(int now = cur[u] ; now != -1 ; now = edge[now].ne){

            int v = edge[now].v;

            if(edge[now].flow && d[v] == d[u] - 1){

                p[v] = now;

                cur[u] = now;

                u = v;

                ok = 1;

                break;

            }

        }

        if(!ok){

            int tm = n * 2 + 1;

            for(int now = head[u] ; now != -1 ; now = edge[now].ne){

                if(edge[now].flow)  tm = min(tm, d[edge[now].v]);

            }

            if(--num[d[u]] == 0)    break;

            num[d[u] = tm + 1]++;

            cur[u] = head[u];

            if(u != s)  u = edge[p[u]].u;

        }

    }

    return flow;

}

int out[MAXN], out_cnt;

void dfs(int u)

{

    out[out_cnt++] = u;

    vis[u] = 1;

    u = 2 * u;

    for(int now = head[u] ; now != -1 ; now = edge[now].ne){

        int v = edge[now].v;

        if(vis[v / 2] == 0 && edge[now].flow == 0 && v != 0){

            dfs(v / 2);

        }

    }

}

int main()

{

    freopen("path10.in", "r", stdin);

    while(scanf("%d%d", &n, &m) != EOF){

        init();

        int ans = n - ISAP(0, 1);

        memset(vis, 0, sizeof(vis));

        for(int i = 1 ; i <= n ; i++){

            if(vis[i] == 0){

                out_cnt = 0;

                dfs(i);

                int f = 1;

                for(int j = 0 ; j < out_cnt ; j++){

                    if(f)   f = 0;

                    else    printf(" ");

                    printf("%d", out[j]);

                }

                printf("\n");

            }

        }

        printf("%d\n", ans);

    }

    return 0;

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值