LOJ6001 「网络流 24 题

来源:互联网 发布:编程语言c 怎么用 编辑:程序博客网 时间:2024/06/06 21:00

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

题意:
  现在您有n个实验要做,完成第i个实验奖励相应的RMB,但是每个实验需要相应的仪器,一共m个仪器,第i仪器启动起来要相应的RMB,请您通过安排使得收益坠大。要求输出方案。

题解:
  这道题是求一个坠大权闭合子图(话说这只图本来就是闭合的)。啊啊啊她有负边怎么办怎么办,好难过

  首先呢,闭合图就是原图的一个子图,如果一个点u在这个子图内,那么它连出去的所有点v也要在这个子图内。最大权闭合图就是点的权值和最大的闭合图。

  先来分析一下:

  1. 这是一个二分图,每个实验向需要的仪器连有向边,实验的点权为正,仪器的点权为负,要求最大权闭合图。

  2. 这是一个选或不选的问题,所以可以转化成最小割的模型,把选的归为S集,不选的归为T集。但是要求获利最大,最小割是最小,所以我们要换个角度,要求扣除的钱最少,因为所有实验的前都加起来是一定的。

  3. 在最小割中,如果把S到所有试验表示的点连一条容量为奖励的钱(A类弧),所有仪器到T连一条容量为启动仪器的钱(B类弧),如果把A类弧割掉了,那么对应的那个实验就归到了T集,也就是不做了,那么就会有损失。如果把B类弧割掉了,那么相应的那个仪器归到了S集,也就是有损失。所有最小割就是使得损失最少的方案。

  所以,我萌这么建边:

1.增加源点S和汇点T
2.从S到所有实验连一条边,容量为其获利,从所有仪器到T连一条边,容量为其花费。
3.从每个实验到相应的仪器连容量为+的边。

  最后总的正的边权减去MinCut,即是答案。

  如何输出方案?

  最后一次BFS不同的时候还能被访问到的点就是呃,留下来的点。

# include <bits/stdc++.h># define N 1010class Network  {private :    struct edge  {        int to, w, nxt ;        edge ( ) {        }        edge ( int to, int w, int nxt ) : to ( to ), w ( w ), nxt ( nxt ) {        }      } g [N << 1] ;    int head [N], cur [N], ecnt ;    int S, T , dep [N] ;    std :: bitset < N > vis ;    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 ) ) ;        vis.reset ( ) ;        dep [S] = 1 ;        vis [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 ;                    vis [v] = 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 m, int S, int T, int sum )  {        int mincut = dinic ( S, T ) ;        for ( int i = 1 ; i <= n ; ++ i )  if ( vis [i] )  printf ( "%d ", i ) ;        puts ( "" ) ;        for ( int i = n + 1 ; i <= n + m ; ++ i )  if ( vis [i] )  printf ( "%d ", i - n ) ;        printf ( "\n%d\n", sum - mincut ) ;    }} Lazer ;int main ( )  {    int n, m ;    int sum ( 0 ) ;    scanf ( "%d%d", & n, & m ) ;    const int S = n + m + 1, T = n + m + 2 ;    for ( int i = 1 ; i <= n ; ++ i )  {        int pi, id, c ;        scanf ( "%d", & pi ) ;        sum += pi ;        Lazer.add_edge ( S, i, pi ) ;        while ( ( c = getchar ( ) ) != '\n' && c != '\t' && c != '\r' )  {            scanf ( "%d", & id ) ;            Lazer.add_edge ( i, id + n, 0x3f3f3f3f ) ;        }    }    for ( int i = 1 ; i <= m ; ++ i )  {        int ci ;        scanf ( "%d", & ci ) ;        Lazer.add_edge ( i + n, T, ci ) ;    }    Lazer.display ( n, m, S, T, sum ) ;    return 0 ;}

小结:

最大权闭合图的通用解法:S到正权值的点连边,容量为其权值,负权值的点到T连边,容量为其绝对值,然后原图中的边容量为+ans = 所有正权和 - 最小割。具体证明可以参考胡伯涛的论文。