LOJ6002 「网络流 24 题 - 3」 最小路径覆盖 坠小路径覆盖数 二分图坠大匹配

大家都很强, 可与之共勉。

题意:

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

题解:

  转化为二分图坠大匹配的问题,把每个点 u 拆为左边的点ux和右边的点 uy ,然后对于一条边 (u,v) ,就把 (ux,vy) 连一条流量为 x(x[1,+)) 的弧。

  求出它的坠大匹配,用总点数减去就得到了最小路径覆盖。

证明:

我们认为一开始每个点都是独立的为一条路径,总共有 n 条不相交路径。我们每次在二分图里找一条匹配边就相当于把两条路径合成了一条路径,也就相当于路径数减少了1。所以找到了几条匹配边,路径数就减少了多少。所以有最小路径覆盖=原图的结点数-新图的最大匹配数。

输出方案的话,直接在里面找流量为 0 <script type="math/tex" id="MathJax-Element-20">0</script>的边就好了……详见代码

# include <bits/stdc++.h>

# define N 2010

class Network  {
private :
    struct edge  {
        int to, w, nxt ;
        edge ( ) {        }
        edge ( int to, int w, int nxt ) : to ( to ), w ( w ), nxt ( nxt ) {        }  
    } g [60010 << 1] ;

    int head [N], cur [N], ecnt ;
    int S, T , dep [N] ;

    inline int dfs ( int u, int a )  {
        if ( u == T || ! a )  return a ;
        int flow = 0, v, f ;
        for ( int& i = cur [u] ; i ; i = g [i].nxt )  {
            v = g [i].to ;
            if ( dep [v] == dep [u] + 1 )  {
                f = dfs ( v, std :: min ( g [i].w, a - flow ) ) ;
                g [i].w -= f, g [i ^ 1].w += f ;
                flow += f ;
                if ( a == flow )  return a ;
            }
        }
        if ( ! flow )  dep [u] = -1 ;
        return flow ;
    }

    inline bool bfs ( int S, int T )  {
        static std :: queue < int > q ;
        memset ( dep, 0, sizeof ( int ) * ( T + 1 ) ) ;
        dep [S] = 1 ;
        q.push ( S ) ;
        while ( ! q.empty ( ) )  {
            int u = q.front ( ) ;  q.pop ( ) ;
            for ( int i = head [u] ; i ; i = g [i].nxt )  {
                int& v = g [i].to ;
                if ( g [i].w &&  ! dep [v] )  {
                    dep [v] = dep [u] + 1 ;
                    q.push ( v ) ;
                }
            }
        }
        return dep [T] ;
    }
public :
    Network ( )  {    ecnt = 1 ; }

    inline void add_edge ( int u, int v, int w )  {
        g [++ ecnt] = edge ( v, w, head [u] ) ;     head [u] = ecnt ;
        g [++ ecnt] = edge ( u, 0, head [v] ) ;     head [v] = ecnt ;
    }

    inline int dinic ( int S, int T )  {
        this -> S = S, this -> T = T ;
        int rt = 0 ;
        while ( bfs ( S, T ) )    {
            memcpy ( cur, head, sizeof ( int ) * ( T + 1 ) ) ; 
            rt += dfs ( S, 0x3f3f3f3f ) ;
        }
        return rt ;
    }

    void display ( int n, int S, int T )  {

        int ans = n - dinic ( S, T ) ;

        static std :: bitset < N > vis ;
        vis.reset ( ) ;
        for ( int i = 1 ; i <= n ; ++ i )
            if ( ! vis.test ( i ) )  {
                int curr = i ;
                for ( bool found = 0 ; ; found = 0 )  {
                    printf ( "%d ", curr ) ;
                    vis.set ( curr ) ;
                    for ( int i = head [curr] ; i ; i = g [i].nxt )  {
                        int& v = g [i].to ;
                        if ( g [i].w == 0 && v > n && v <= 2 * n )  {
                            curr = v - n ;
                            found = 1 ;
                            break ;
                        }
                    }
                    if ( found == 0 )  {
                        puts ( "" ) ;
                        break ;
                    }
                }
            }

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

int main ( )  {
    int n, m ;
    scanf ( "%d%d", & n, & m ) ;

    const int S = n * 2 + 1, T = n * 2 + 2 ;

    for ( int i = 1 ; i <= n ; ++ i )  {
        Lazer.add_edge ( S, i, 1 ) ;
        Lazer.add_edge ( i + n, T, 1 ) ;
    }

    while ( m -- )  {
        static int u, v ;
        scanf ( "%d%d", & u, & v ) ;
        Lazer.add_edge ( u, v + n, 1 ) ;
    }

    Lazer.display ( n, S, T ) ;
    return 0 ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值