网络流

网络

什么是网络?网络其实就是有向带权图。为什么要叫网络,是因为权值是容量,容量意味着可以在单位时间内经过的上限,但是可以比上限小。

形象的奖就是一堆水管,每个水管有个大小限制流量。

中间略去一堆显然的性质。

最大流问题

寻找网络G上可能的最大流量(和一个有最大流量的可行流方案),即为网络G上的最大流问题

核心:

增广路径

若P是网络中连结源点s和汇点t的一条路,我们定义路的方向是从s到t,则路上的弧有两种:
 前向弧—弧的方向与路的方向一致。前向弧的全体记为P+;
 后向弧—弧的方向与路的方向相反。后向弧的全体记为P-;
设F是一个可行流,P 是从s到t的一条路,若P满足下列条件:
在P+的所有前向弧(u,v)上,0≦f(u,v) < C(u,v);
在P-的所有后向弧(u,v)上,0 < f(u,v) ≦C(u,v);
则称P是关于可行流F的一条可增广路径。
这里我们有一个显然的定理:
**最大流定理
如果残留网络上找不到增广路径,则当前流为最大流;反之,如果当前流不为最大流,则一定有增广路径。**
可以度娘搜索证明方式(算法导论上也有)
那么我们现在的问题就是找增广路径

方法①

大力dfs:

using namespace std;
const int oo=1000000;
int n,m,    a[3000][3000],  sum=0,  forward;  
bool vis[3000],check=true;

void init()
{
    cin>>m>>n;
    memset(a,0,sizeof(a));
    for (int i=1;i<=m;i++)
    {
        int x,y,z;
        cin>>x>>y>>z;
        a[x][y]+=z;  //这是图论里经常出现的吭,表示可能出现重边。
    }
}
void dfs(int k,int l) //k是顶点的编号,l是最小的增广流量
{
    vis[k]=true; //dfs必须有的标记
    if (k==n)  //找到汇点,
    {
        check=true; //全局变量,标记存在增广路径
        sum+=l; forward=l;  //流量可以扩充l。并记录下l,在回溯时进行流量操作。     
        return;
    }
    for (int i=1;i<=n;i++)
    {
        if ((a[k][i]>0)&&(!vis[i]))  //dfs固有的东西。
        {
            dfs(i,min(a[k][i],l));
            if (check) //这里是dfs后,回溯的位置
            {
                a[k][i]-=forward;   //正向减去流量
                a[i][k]+=forward;  //逆向(可退流边)加上这个流量
                return;         
            }
        }
    }
}
int main()
 {
     init();
     while (check)  //只要还能找到可增广路,就一直找下去。
     {
         check=false;
         memset(vis,false,sizeof(vis));
         dfs(1,oo);
     }
     cout<<sum<<endl;
     return 0;
 }
方法②:dinic 算法

先bfs一遍(注意只遍历容量不为0的边),求出所有节点的层数,用level数组记录。

接着就做网络流,其基本原理就是:在现有的level基础上,只按照level递增顺序找增广路径,且有一点不同的是当找到一条s–>t的路径的时候,不是直接结束,而是从当前路径上最小层次的顶点(顶点,当前路径最大流量的边上的点,就是下图的红点或绿点K)再次寻找可行流(一定要将当前层次的可行流全部找完啊!)

整个dfs过程分为2个操作。如果p的最后一个顶点为汇点,也就是说找到了增广路,那么对p增广,注意到增广后一定有一条或多条p中的边被删除了。这时,我们使增广路径后退至p中从源点可到达的最后一个顶点。
如果p的最后一个顶点不为汇点,那么观察最后那个的顶点u 。若在层次图中存在从u连出的一条边,比如(u,v),我们就将顶点v放入路径p中,继续dfs遍历;否则,点u对之后的dfs遍历就没有用了,我们将点u以及层次图中连到u的所有边删除,并且在p中后退一个点。

Dfs过程将会不断重复这2个操作,直到从源点连出的边全部被删除为止。
整个dinic的过程就是不断地先bfs,再dfs,直到bfs不能得到汇点的层数为止。

附上软件:

#include<bits/stdc++.h>
using namespace std;
const long long oo=165430000;
bool check;
long long n,m,lazy,ans,linkk[1100],t,deep[1100],head,tail,q[1100];
struct node
{
    long long n,y,v;
}e[1100000];
long long read() 
{ 
    bool flag=true; 
    long long num=0;char c=getchar(); 
    for(;c<'0'||c>'9';c=getchar())if(c=='-') flag=false; 
    for(;c>='0'&&c<='9';c=getchar()) 
    num=(num<<3)+(num<<1)+c-48; 
    if(flag) return num; 
     else return -num; 
} 
void init()
{
    m=read();n=read();
    int aa,bb,cc;
    t=1;
    for(int i=1;i<=m;++i)
     {
        aa=read();bb=read();cc=read();
        e[++t].n=linkk[aa];
        e[t].y=bb;
        e[t].v=cc;
        linkk[aa]=t;
        e[++t].n=linkk[bb];
        e[t].y=aa;
        e[t].v=0;
        linkk[bb]=t;
     }
}
int dfs(int x,long long lazy)
{
    if(x==n)return lazy;
    int nowlazy=0;
    int d=0;
    for(int i=linkk[x];i&&nowlazy<lazy;i=e[i].n)
     {
      int v=e[i].y;
      if(deep[v]==deep[x]+1&&e[i].v>0)
       if(d=dfs(v,min(e[i].v,lazy-nowlazy)))
        {
          nowlazy+=d;
          e[i].v-=d;
          e[i^1].v+=d;
        } 
     }
    if(nowlazy==0) deep[x]=-1;
    return nowlazy;
}
void bfs()
{
    memset(deep,-1,sizeof(deep));
    deep[1]=0;head=tail=1;
    q[1]=1; 
    while(head<=tail)
     {
       int v=q[head];
       for(int i=linkk[v];i;i=e[i].n)
        if(deep[e[i].y]==-1&&e[i].v>0) deep[e[i].y]=deep[v]+1,q[++tail]=e[i].y;
       head++;
     } 
    if(deep[n]!=-1) check=true;
    return;
}
int main()
{
    init();
    check=true;
    while(check)
     {
        int d=0;check=false;
        bfs();
        if(!check) break;
        while(d=dfs(1,oo)) ans+=d;
     }
    printf("%lld",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值