网络流专题

写在最前面:"嘿嘿,这是多久前的专题了"

来来来,先来看看我都做了些什么题:

网络吞吐量(network)
【ZJOI2009】狼和羊的故事
【Usaco 2005 NOV Gold】小行星群
【NOIP2013模拟联考2】摘取作物(pick)
Dining
【2011集训队出题】圈地计划
【NOI2006】最大获利

哇,好"多"啊!

嘿嘿,那道是~~

基本概念

网络流,顾名思义,就是给出边权,求一个网络的最流量(或者再加上一些别的东西),

自个想去 详情请查询百度

省略…

一大堆简单的东西这里就不写出来了~

说了这么多废话,现在开讲了!!!

连边技巧

As wo all know,网络流都是要连双向边的,So,if要用数组模拟链表的话,会非常麻烦,因为要多一个反向弧的数组,这里介绍一种技巧
连边肯定是边和反向边一起连的,连续连2条,
也就是说:
这两条边的编号除末尾的二进制不一样意外,前面的二进制都一样!!!
这两条边的编号除末尾的二进制不一样意外,前面的二进制都一样!!!
这两条边的编号除末尾的二进制不一样意外,前面的二进制都一样!!!
这两条边的编号除末尾的二进制不一样意外,前面的二进制都一样!!!
如:

类型 编号 二进制
10 1010
反向边 11 1011
11 1100
反向边 12 1101

然后我们又发现,有一个东西叫“异或”
我们再发现,边i的反向边为i^1

费用流

这里介绍两种方法

SPFA费用流

这个费用流为SPFA水法!!!

SPFA水法!!!SPFA水法!!!SPFA水法!!!

每次用SPFA找一遍最短路,记录路径,然后减掉此路线上的最大流量直到找不到路径为止

不给B 1了~

ZKW费用流

这里的费用流为ZKW正解!!!

ZKW正解!!!ZKW正解!!!ZKW正解!!!

嘿嘿~~

这是一个非常优秀的算法

此算法是由ZKW大牛创立,主要思想仍然是找增广路,只是有了一些优化在里边。原来我们找增广路主要是依靠最短路算法,如SPFA。因此此算法的时间复杂度主要就取决于增广的次数和每次增广的耗费。由于每一次找增广路是都是重新算一遍,有时会浪费时间,如果我们能够缩短找增广路的时间,那必定会大大地优化算法。

值得注意的是,这个算法可以令dis[i]为点i到t的距离。

对于每一条由i引向j的边,必有dis[j]<=dis[i]+map[i][j];既然满足这样的恒等式,我们就可以借用KM算法(我也不知道是什么)的调整思想来寻求最短路,每次只走dis[j]=dis[i]+map[i][j]的路径,一旦不存在到达终点的路径,就扫描每一条边,找到最小的距离增加值,使得有至少一条新边被加入相等子图。 —-ZLT

嘿嘿,懒惰的我直接Copy ZLT的段子

这个我弄了好久才弄懂

意思大概是这样:
每次找一个最小代价的增加值,并且加上去,再跑一遍特别的最大流

Created with Raphaël 2.1.0 输入 找最小的增加(能走的)路径 有新增加的路径 把全图能走到的点都加上这个值 跑网络流, (当dis[j]=dis[i]+map[i][j]走); 标记数组清零; 输出答案 yes no

(嗯,差不多就这样吧)

B在这里:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fo1(i,a,b) for(i=a;i>=b;i--)
#define ll long long 
#define maxlongint 2147483647
#define N 1500
using namespace std;
void read(int &n)
{
    char ch=getchar();
    while(ch==' ' || ch=='\n') ch=getchar();int q=0,w=1;if(ch=='-') {w=-1;ch=getchar();}
    while(ch>='0' && ch<='9' || ch=='-') {q=q*10+ch-48;ch=getchar();}n=q*w;
}
int i,j,m,n,ans,q,w,e,e1,S,T,ans1;
int a[N],B[N*2][2],B0,tl[N],F[N*2][2];
int d1[N];
bool z[N];
void join(int q,int w,int e,int e1)
{
    B0++;
    if(a[q]==0) a[q]=B0;
        else B[tl[q]][0]=B0;
    tl[q]=B0;
    B[B0][0]=0;
    B[B0][1]=w;
    F[B0][0]=e;
    F[B0][1]=e1;
    B0++;
    if(a[q]==0) a[w]=B0;
        else B[tl[w]][0]=B0;
    tl[w]=B0;
    B[B0][0]=0;
    B[B0][1]=q;
    F[B0][0]=0;
    F[B0][1]=-e1;
}
bool OK()
{
    int m1=maxlongint;
    fo(i,1,n)
        if(z[i])for(int j=a[i];j;j=B[j][0])
                if(!z[B[j][1]] && F[j][0]) m1=min(m1,d1[B[j][1]]+F[j][1]-d1[i]);
    if(m1==maxlongint) return 0;
    fo(i,1,n)
        if(z[i])
        {
            z[i]=0;
            d1[i]+=m1;
        }
    return 1;
}
int aug(int q,int e)
{
    if(q==T) 
    {
        ans1+=d1[S]*e;
        return e;
    }
    z[q]=1;
    int t,i,w;
    for(i=a[q];i;i=B[i][0])
    {
        w=B[i][1];
        if(!z[w] && d1[w]+F[i][1]==d1[q] && F[i][0])
        {
            t=aug(w,min(e,F[i][0]));
            if(t)
            {
                F[i][0]-=t;
                F[i^1][1]+=t;
                return t;
            }
        }
    }
    return 0;
}
int main()
{
    read(n); read(m); 
    B0=1; S=1; T=n;
    fo(i,1,m)
    {
        read(q); read(w); read(e); read(e1);
        join(q,w,e,e1);
    }
    z[S]=1;
    while(OK())
    {
        while(q=aug(S,maxlongint)) 
        ans+=q,memset(z,0,sizeof(z));
    }
    printf("%d %d \n",ans,ans1);
    return 0;
}

二分图匈牙利匹配

二分图,网络流经典图形,这里就省略了......
二分图有两种跑法,一种是普通网络流,另一种就是匈牙利匹配了,

这是一个很偷懒2的算法!!!

主要思想

我们先假定我们每次就随便选一个可以连过去的点连过去,那么就肯定会遇到重复的情况(同一个点要被连大于流量次),
遇到重复,我们就找一下,能不能把之前连的某条边改道,如果可以改道,就把它改道,并把我当前这个点连上,正确性显然。

可以用递归实现
类型 复杂度
普通网络流(n个点,m条边)
O(n2m)
匈牙利匹配(n个点,m条边
O(mn)

偷懒3对不对!!!

总结

能连就连,不能连就改,改不了就不连
嗯,就是这样~

ATTENTION!!!:尽量不要用memset(P:fillchar),会超级慢

不给B了~

  1. 标程(均为C++). ↩
  2. 效率高啊!!! ↩
  3. 效率高啊!!! ↩

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