zoukankan      html  css  js  c++  java
  • 「网络流24题」最小路径覆盖问题

    传送门:>Here<

    题意:求DAG的最小路径覆盖并输出方案。所谓最小路径覆盖是指,将原图分为若干条路径,任意两条路径不能有公共点,要使路径数量尽可能少

    思路分析

    依然能够联系到二分图。事实上这个问题在学二分图的时候提到过,然而当时并没有弄明白……

    公式:DAG的最小路径覆盖 = 顶点数 - 最大匹配

    千万不要弄混淆的是这里的最大匹配并不是指直接在DAG上做最大匹配,而是需要拆点然后搞。

    具体过程如下:

    原图共有N个点。将每个点$i$拆成两个点,记为$X_i$与$Y_i$,对于任意一条原图中的边$(u,v)$,连接$(X_u, Y_v)$(有向边)。很容易发现,左半部对应着每条边的起点,右半部对应着终点。然后以X为左半部,Y为右半部做二分图的最大匹配,得到答案ans,则最小路径覆盖就是N-ans。

    为什么最小路径覆盖就是N-ans呢?

    假设拆点建的图中一条匹配边都没有——也就意味着没有边。此时每一个顶点为一条路径,最小路径覆盖为N。

    然后,在拆点建的图中增加一条匹配边,我们发现这对应着原图中的一个点融入了另一条路径,因此最小路径覆盖要-1。进一步我们发现,每增加一条能够匹配的边,就意味着原图中的又一个点加入到了某一条路径当中。因此最小路径覆盖会-1。因此有多少条匹配边就减几次。所以要让答案最小,也就是让匹配边尽量多。问题转化为了二分图的最大匹配问题

    那为什么我们要拆点呢?原因在于原图是一个有向图,对于一个路径内部的点(非起点、终点),一定有一条边进入它,一条边从它出。如果直接拿原图做二分图匹配不会容许一个点有两条边的情况。而拆点以后,左侧的点仅仅作为起点,右侧的点仅仅作为终点,因此这种情况对应到每一个点依然只有一条边。如果一个点出发有两条边,对应着原图中一个点作为起点有两条边,此时由于要求最小路径覆盖,必须舍弃一条边,二分图匹配会自动舍弃另一条边。所以二分图内的所有匹配都不会发生这种矛盾的情况,也就意味着每有一条匹配边,就可以多将一个点归入一条路径

    以上是公式的原理,下面来谈如何输出方案。

    对于每一个起点,我们记录它出发是否有一条饱和弧到达一个终点,如果有,意味着它一定不是终点。我们对于每一个点记录一个由它出发到达的点(sec),以及以它作为终点的边的那个起点(pre)。如果当前点不作为任何一条边的终点,那它就是起点;反之,如果不到达任何一个其他点,那么它就是终点。因此我们只需要枚举每个点以及由他延伸出去的弧,查看每条弧是否饱和,若饱和则说明这是匹配边,记录sec和pre即可

    Code

    /*By DennyQi*/
    #include <cstdio>
    #include <queue>
    #include <cstring>
    #include <algorithm>
    #define  r  read()
    #define  Max(a,b)  (((a)>(b)) ? (a) : (b))
    #define  Min(a,b)  (((a)<(b)) ? (a) : (b))
    using namespace std;
    typedef long long ll;
    const int MAXN = 10010;
    const int MAXM = 10010;
    const int INF = 1061109567;
    inline int read(){
        int x = 0; int w = 1; register int c = getchar();
        while(c ^ '-' && (c < '0' || c > '9')) c = getchar();
        if(c == '-') w = -1, c = getchar();
        while(c >= '0' && c <= '9') x = (x << 3) +(x << 1) + c - '0', c = getchar(); return x * w;
    }
    int N,M,S,T,x,y,ans_min;
    int first[MAXM*2],nxt[MAXM*2],to[MAXM*2],cap[MAXM*2],flow[MAXM*2],num_edge=-1;
    int level[MAXN],cur[MAXN],pre[MAXN],sec[MAXN];
    queue <int> q;
    inline void add(int u, int v, int c, int f){
        to[++num_edge] = v;
        cap[num_edge] = c;
        flow[num_edge] = f;
        nxt[num_edge] = first[u];
        first[u] = num_edge;
    }
    inline bool BFS(){
        memset(level, 0, sizeof(level));
        while(!q.empty()) q.pop();
        q.push(S);
        level[S] = 1;
        int u,v;
        while(!q.empty()){
            u = q.front(); q.pop();
            for(int i = first[u]; i != -1; i = nxt[i]){
                v = to[i];
                if(!level[v] && cap[i]-flow[i]>0){
                    level[v] = level[u]+1;
                    q.push(v);
                }
            }
        }
        return level[T]!=0;
    }
    int DFS(int u, int a){
        if(u == T || a == 0) return a;
        int ans = 0, v, _f;
        for(int& i = cur[u]; i != -1; i = nxt[i]){
            v = to[i];
            if(level[u]+1==level[v] && cap[i]-flow[i]>0){
                _f = DFS(v, Min(a,cap[i]-flow[i]));
                ans += _f, a -= _f;
                flow[i] += _f, flow[i^1] -= _f;
                if(a == 0) break;
            }
        }
        return ans;
    }
    inline void Dinic(){
        int ans = 0;
        while(BFS()){
            for(int i = S; i <= T; ++i) cur[i] = first[i];
            ans += DFS(S, INF);
        }
        ans_min = N - ans;
    }
    int main(){
        N=r,M=r;
        S = 0, T = 2*N+2;
        memset(first, -1, sizeof(first));
        for(int i = 1; i <= M; ++i){
            x=r,y=r;
            add(x, y+N, 1, 0);
            add(y+N, x, 0, 0);
        }
        for(int i = 1; i <= N; ++i){
            add(S, i, 1, 0), add(i, S, 0, 0);
            add(i+N, T, 1, 0), add(T, i+N, 0, 0);
        }
        Dinic();
        for(int i = 1; i <= N; ++i){
            int v;
            for(int j = first[i]; j != -1; j = nxt[j]){
                v = to[j];
                if(cap[j]-flow[j]==0 && cap[j]==1){
                    pre[v-N] = i;
                    sec[i] = v-N;
                }
            }
        }
        for(int i = 1; i <= N; ++i){
            if(!pre[i]){
                int u = i;
                while(sec[u] != 0){
                    printf("%d ", u);
                    u = sec[u];
                }
                printf("%d
    ",u);
            }
        }
        printf("%d", ans_min);
        return 0;
    }

     

  • 相关阅读:
    C++ 栈和队列
    epoll 实际使用
    js数组的操作
    nodejs 基本类型和语法
    epoll 简单介绍及例子
    epoll 应用
    linux 查看系统状态方法
    struct stat结构体的详解和用法
    Asp.Net_获取IP地址
    Asp.Net_的传值跟存储值操作
  • 原文地址:https://www.cnblogs.com/qixingzhi/p/9418690.html
Copyright © 2011-2022 走看看