网络流

一、最大流

1.网络流是带反悔的贪心,先求出一个当前的可行流,再寻找增广路进行增广(其实就是再找新的路多输出一点流量)。为了实现网络流的增广,需要给每条边都建一个反向边,有大小为delta的流进入这条边时,这条边的容量-delta,其反向边的容量+delta。

  这样做的原理是斜对称性。x向y流了flow的流量,就相当于y向x流了-flow的流量,两者是等价的,因此可以建反向边来支持往返流动,达到增广的目的。

  举个例子,下面的网络中,容量为0的边为反向边

网络流_第1张图片

 

 

  在红色路径注入1单位水,那么这些边的容量都减去1,其反向边容量都加1

 

网络流_第2张图片

 

 

 

  很显然这个流不是最大流,如果没有反向边的话就无路可走,现在来看,中间的反向边可以向上流过1单位流量,这样就出现了一条增广路(蓝色的部分)

网络流_第3张图片

  现在从S到T已经不相通了,那么最大流就是2,做到这里会发现,节点1和2之间实际上根本没有水流,而我们第一条可行流走的是S-1-2-T,也就是说后来1-2这条路上反悔了,这就是网络流的精髓。

  但是不必纠结水流到底是怎么流的,比如有人看到这里就会想,这不就是S-1-T和S-2-T吗,并不是,实际流的是S-1-2-T和S-2-1-T,两个有什么不同呢,前者的思路是一次性流过了两个流,后者的思路是先走了第一个流,再走的第二个流,但两个流法的效果是一样的。前面那种流法看起来更简单,实际上很难实现,怎么保证这个流一定不会走1-2呢,不大好解决,我猜正因为这个,才有人想出分步灌流量,给出反悔的余地。

 

2.dinic算法

  dinic算法是EK的优化,所以一般来说掌握了dinic就可以,下面说一下dinic的算法步骤:

  (1)利用bfs对原来的图进行分层,标记每个点的层次,这个标记的含义是当前节点距离源点的最短距离。(构建层次图时所走的边的残余流量必须大于0)

  (2)用dfs寻找从源点到汇点的通路(增广路),每条增广路要保证层次递增。

  (3)重复步骤2,找不到增广路的时候,将新增流量加入答案,然后重复步骤1,重新建立层次图,直到从源点不能到达汇点为止。

 

/*
    一定要建边权为0的反向边
    别忘了dinic的三个优化:
    1、当前弧优化,访问这个点的出边时,从上一次访问的下一条边开始
    2、当增广到某个点时,增广过程中,已出去的流量==进来的,停止增广;增广完毕时,出去的流量<进来的流量,标记这个点,以后不再访问此点
    3、分层时,找到汇点后即刻return,不要等到队列为空

*/
#include
#include
#include
#define maxn 10010
using namespace std;
int n,m,ans,head[maxn],cur[maxn],f[maxn],lev[maxn],num=1,s,t;
struct node{
    int to,pre,cap;
}e[100010*2];
void Insert(int from,int to,int v){
    e[++num].to=to;
    e[num].cap=v;
    e[num].pre=head[from];
    head[from]=num;
}
bool bfs(int st){
    queue<int>q;
    q.push(st);
    for(int i=1;i<=n;i++)cur[i]=head[i],lev[i]=-1;
    lev[st]=0;
    while(!q.empty()){
        int now=q.front();q.pop();
        for(int i=head[now];i;i=e[i].pre){
            int to=e[i].to;
            if(lev[to]==-1&&e[i].cap>0){
                lev[to]=lev[now]+1;
                q.push(to);
                if(to==t)return 1;
            }
        }
    }
    return 0;
}
int dinic(int now,int flow){
    if(now==t)return flow;
    int rest=0,delta;
    for(int &i=cur[now];i;i=e[i].pre){
        int to=e[i].to;
        if(e[i].cap>0&&lev[to]==lev[now]+1){
            delta=dinic(to,min(flow-rest,e[i].cap));
            if(delta){
                e[i].cap-=delta;e[i^1].cap+=delta;
                rest+=delta;if(rest==flow)break;
            }
        }
    }
    if(rest!=flow)lev[now]=-1;
    return rest;
}
int main(){
    scanf("%d%d%d%d",&n,&m,&s,&t);
    int x,y,z;
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        Insert(x,y,z);Insert(y,x,0);
    }
    while(bfs(s))
        ans+=dinic(s,0x7fffffff);
    printf("%d",ans);
}

 

2018 ACM/ICPC 南京 I题 Magic Potion(最大流)

题意:有n个英雄,m个怪物,每个英雄有一个可打的怪物集合,一个英雄只能打一个怪物。有k瓶魔法药水,一个英雄最多喝一瓶魔法药水,喝完之后可以多打一个怪物。问最多有几个怪物会被打死。

解:英雄和怪物之间的连线不必多说,主要是起点的设置。设了三个S,S是最终的起点,S1表示正常打怪的起点,S2表示喝药水的额外打怪起点,很显然,S应该向S1和S2分别建权值为n和k的边,S1和S2都向每个英雄建权值为1的边,效果如下图:

网络流_第4张图片

 

#include
#include
#include
#include
#define maxn 510*3
#define maxm 300010
using namespace std;
int n,m,k,S,S1,S2,T;
int num=1,head[maxn],cur[maxn],lev[maxn];
struct node{
    int to,pre,cap;
}e[maxm*2];

void Insert(int from,int to,int v){
    e[++num].to=to;
    e[num].cap=v;
    e[num].pre=head[from];
    head[from]=num;
}

bool bfs(int s){
    queue<int>q;
    for(int i=1;i<=n+m+4;i++)cur[i]=head[i],lev[i]=-1;
    lev[s]=0;q.push(s);
    while(!q.empty()){
        int now=q.front();q.pop();
        for(int i=head[now];i;i=e[i].pre){
            int to=e[i].to;
            if(lev[to]==-1&&e[i].cap>0){
                q.push(to);
                lev[to]=lev[now]+1;
//                if(to==T)return 1;
            }
        }
    }
    if(lev[T]>=0)return 1; 
    return 0;
}

int dinic(int now,int flow){
    int rest=0;
    if(now==T)return flow;
    for(int &i=cur[now];i;i=e[i].pre){
        int to=e[i].to;
        if(lev[to]>lev[now]&&e[i].cap>0){
            int delta=dinic(to,min(e[i].cap,flow-rest));
            if(delta){
                rest+=delta;
                e[i].cap-=delta;
                e[i^1].cap+=delta;
                if(rest==flow)break;
            }
        }
    }
    if(rest1;
    return rest;
}

int main(){
    scanf("%d%d%d",&n,&m,&k);
    S=1;S1=2;S2=3;T=3+n+m+1;
    Insert(S,S1,n);Insert(S1,S,0); 
    Insert(S,S2,k);Insert(S2,S,0);
    int x,y;
    for(int i=1;i<=n;i++){
        Insert(S1,3+i,1);Insert(3+i,S1,0);
        Insert(S2,3+i,1);Insert(3+i,S2,0);
        scanf("%d",&x);
        for(int j=1;j<=x;j++){
            scanf("%d",&y);
            Insert(3+i,3+n+y,1);
            Insert(3+n+y,3+i,0);
        }
    }
    for(int i=n+4;i<=n+m+3;i++){
        Insert(i,T,1);
        Insert(T,i,0);
    }
    int ans=0;
    while(bfs(S))
        ans+=dinic(S,0x7fffffff);
    printf("%d\n",ans);
}

 

二、最小割

  最小割=最大流

  一个割表示将这些边割掉之后,S与T不再连通。首先要说的是,网络中的任何一个一个割一定大于等于任何一个流,可以理解为在割掉这些边之后,所有原本应该向后流的水,全都流失了,这些流失的水(V)一定小于等于割掉边的容量和(Cap),大于等于最大流(maxFlow)(因为后面的边仍然会约束流量),也就是$Cap\geq V \geq maxFlow$,则有$Cap\geq maxFlow$。

  所以最小的割就是使割掉的边的流量完全等于流过的水流,比如有一条水流的路径为1-2-3-4,那么这个水流的大小一定是1-2,2-3,3-4这三条路径中最细的一条,如果3-4最细,就割3-4,将水从3这个口子放走,那么这条通道的水流就全部流失了。这样以此类推,就可以得出结论:最小割=最大流。

 

你可能感兴趣的:(网络流)