网络流(学习笔记)

  网络流(学习笔记)

 (PS:本文纯粹复习使用,对于想看图的童鞋不是很友好)

   

  我们想象一下自来水厂到你家的水管网是一个复杂的有向图,每一节水管都有一个最大承载流量。自来水厂不放水,你家就断水了。但是就算自来水厂拼命的往管网里面注水,你家收到的水流量也是上限(毕竟每根水管承载量有限)。你想知道你能够拿到多少水,这就是一种网络流问题。

  这里,我并不喜欢直接扔一大堆定理,比如什么斜对称性啊,流量守恒啊啥啥的,当你学会网络流的基础算法之后,我相信这些都不重要

这里我并没有按照问题的不同而分类,而是就不同的算法来考虑。

  • EK算法(Edmonds Karp算法)

 ——这是一个基于bfs的单路增广算法。

  前置知识:

  网络流里的增广路:如果存在一条路径c(s,t),使得当前的总流量增大,那么这条路径就称为增广路。

  容易发现,如果网络中不存在增广路,那么就能够得到网络流的最大流量。

  那么求最大流问题就转化为求增广路问题,即不断找增广路,直到找不到为止。

  怎样操作呢?我们从s开始bfs,通过残量(边的总容量减去当前流量)大于0的边,每跑到一次t时,我们把改c(s,t)路径上的最小残量找出来,把这条路径灌满:即每一条边都减去这个最小残量。

  这样直到我们发现不存在这样的路径为止。

  那么这里出现了一个疑问:当增广一条路径的时候,你发现另一条路径已经把当前路径上的残量减少,但是从其中的一个点到t仍有可用的残量,这时你会发现,你并不能得到最优解。行吧,这里盗个图:

例如:你已经搜了s->3->5->t,流量是10

你又搜了s->4->5->t,减去第一条路径的流量,新增流量是35

可是你发现,你完全可以搜s->3->t和s->4->5->t,这样流量分别为10,45,显然更大。

可如果你不做任何处理的话,你会发现你在搜过s->3->5->t后,你永远不会再搜到3了,也就不会找到s->3->t这条路径了。

  那么怎么办呢?我们考虑对每条边加一条初始流量为0的反向边。

  当我们先搜s->3->5->t时,我们把正边减去最大流量,反边加上最大流量。

这就告诉了第二条路径s->4->5->t,在到达节点5时,其实有10的流量是通过3留过来的,那么如果把这10流量返回去,再次从3开始跑,若能到达t则相当于找到了一条增广路。进而为第二条路径增大了10残量。

是不是很NB,下面放上code:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int maxn=10010;
const int maxm=100010;
const int inf=1<<30;

struct node
{
    int to,next,dis;
}g[maxm*2];
int head[maxn],cnt=1; 
struct P
{
    int edge,from;
}pre[maxn];
int ans;
int n,m,s,t;
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

inline void addedge(int u,int v,int dis)
{
    g[++cnt].next=head[u];
    g[cnt].to=v;
    g[cnt].dis=dis;
    head[u]=cnt;
}

bool vis[maxn];
bool bfs()
{
    memset(vis,0,sizeof(vis));
    memset(pre,-1,sizeof(pre));
    queue<int>q;
    q.push(s);
    vis[s]=1;
    while(q.size())
    {
        int u=q.front();q.pop();
        for(int i=head[u];i;i=g[i].next)
        {
            int v=g[i].to;
            if(!vis[v]&&g[i].dis)
            {
                pre[v].edge=i;
                pre[v].from=u;
                if(t==v)return 1;
                vis[v]=1;
                q.push(v);
            }
        }
    }
    return 0;
}

  那么最小费用最大流呢?

  其实很简单,我们只需要将bfs换成spfa就可以了。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int maxn=10010;
const int maxm=100010;
const int inf=1<<30;

struct node
{
    int to,next,dis,val;
}g[maxm*2];
int head[maxn],cnt=1; 
struct P
{
    int edge,from;
}pre[maxn];
int dist[maxn];
int ans;
int n,m,s,t;
int cost;
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

inline void addedge(int u,int v,int dis,int val)
{
    g[++cnt].next=head[u];
    g[cnt].to=v;
    g[cnt].dis=dis;
    g[cnt].val=val;
    head[u]=cnt;
}

bool vis[maxn];
bool spfa()
{
    memset(dist,0x3f,sizeof(dist));
    memset(vis,0,sizeof(vis));
    memset(pre,-1,sizeof(pre));
    queue<int>q;
    q.push(s);
    vis[s]=1;
    dist[s]=0;
    while(q.size())
    {
        int u=q.front();q.pop();
        vis[u]=0;
        for(int i=head[u];i;i=g[i].next)
        {
            int v=g[i].to,w=g[i].val;
            if(dist[v]>dist[u]+w&&g[i].dis)
            {
                dist[v]=dist[u]+w;
                pre[v].edge=i;
                pre[v].from=u;
                if(!vis[v])
                {
                    vis[v]=1;
                    q.push(v);
                }
            }
        }
    }
    return dist[t]!=0x3f3f3f3f;
}



int EK()
{
    int ans=0;
    while(spfa())
    {
        int mi=inf;
        for(int i=t;i!=s;i=pre[i].from)
        {
            mi=min(mi,g[pre[i].edge].dis);
        }
        for(int i=t;i!=s;i=pre[i].from)
        {
            g[pre[i].edge].dis-=mi;
            g[pre[i].edge^1].dis+=mi; 
        }
        ans+=mi;
        cost+=mi*dist[t];
    }
    return ans;
}

int main()
{
    n=read();m=read();s=read();t=read();
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read(),z=read(),w=read();
        addedge(x,y,z,w);addedge(y,x,0,-w);
    }
    printf("%d ",EK());
    printf("%d",cost);
}

 

  • Dinic算法

  Dinic算法比EK算法快多辣。它可以实现多路增广。

  具体操作是:先用当前有残量的边构成的子图V',构造一个层次图。即满足dep(i)为点i到起点的边的条数,和树的深度相同,这里我习惯dep[s]=1。

  为什么要这样构造呢,比如:对于路径c(s,u,x,v,t)和c(s,u,y,v,t),其中s->u,v-t的残量是确定的,那么对于u后面的分支,我们可以选择其中的一条进行增广,且这样既不会走回去,也不会对结果产生干扰(如果其中一条路径残量为0,那么下一次分层的时候,肯定不会走这一条,因为这条路径不在V'中,而是增广另一条),并且为多路增广提供基础。

  这里我们使用dfs进行增广,直接上代码吧。

 

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int M=100010;
const int N=10010;
const int inf=0x3f3f3f3f;
struct node
{
    int next,to,w;
}g[M<<1];
int head[N],cnt=1;
int n,m,s,t;
int ans;
bool vis[N];
int dep[N];
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

inline void addedge(int u,int v,int w)
{
    g[++cnt].next=head[u];
    g[cnt].to=v;
    g[cnt].w=w;
    head[u]=cnt;
}

bool bfs()
{
    for(int i=1;i<=n;i++)dep[i]=inf,vis[i]=0;
    queue<int>q;
    q.push(s);
    dep[s]=1;vis[s]=1;
    while(q.size())
    {
        int u=q.front();q.pop();vis[u]=0;
        for(int i=head[u];i;i=g[i].next)
        {
            int v=g[i].to;
            if(dep[v]>dep[u]+1&&g[i].w)
            {
                dep[v]=dep[u]+1;
                if(!vis[v])
                vis[v]=1,q.push(v);
            }
        }
    }
    if(dep[t]!=inf)return 1;
    return 0;
}

int dfs(int u,int flow)
{
    if(u==t)//找到增广路 
    {
        ans+=flow;//这里可以直接累加最大流 
        return flow;//返回当前路径上的最小残量 
    }
    int rest=flow;//当前点的残量 
    for(int i=head[u];i;i=g[i].next)
    {
        int v=g[i].to;
        if(dep[v]==dep[u]+1&&g[i].w)//必须满足层次图 
        {
            int rlow=dfs(v,min(rest,g[i].w));//从当前点开始往t增广,返回的是u->t
                                             //的最小残量 
            if(rlow)
            {
                g[i].w-=rlow;
                g[i^1].w+=rlow;//不解释 
                rest-=rlow;//当前点u的残量被u->t这条路径填充了rlow 
                if(!rest)break;
            }
        }
    }return flow-rest;
}

void dinic()
{
    while(bfs())
    {
        dfs(s,inf);
    }cout<<ans;
}

int main()
{
    n=read();m=read();s=read();t=read();
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read(),z=read();
        addedge(x,y,z);addedge(y,x,0);
    }
    dinic();
}    

 

转载于:https://www.cnblogs.com/THRANDUil/p/10958237.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园整体解决方案是响应国家教育信息化政策,结合教育改革和技术创新的产物。该方案以物联网、大数据、人工智能和移动互联技术为基础,旨在打造一个安全、高效、互动且环保的教育环境。方案强调从数字化校园向智慧校园的转变,通过自动数据采集、智能分析和按需服务,实现校园业务的智能化管理。 方案的总体设计原则包括应用至上、分层设计和互联互通,确保系统能够满足不同用户角色的需求,并实现数据和资源的整合与共享。框架设计涵盖了校园安全、管理、教学、环境等多个方面,构建了一个全面的校园应用生态系统。这包括智慧安全系统、校园身份识别、智能排课及选课系统、智慧学习系统、精品录播教室方案等,以支持个性化学习和教学评估。 建设内容突出了智慧安全和智慧管理的重要性。智慧安全管理通过分布式录播系统和紧急预案一键启动功能,增强校园安全预警和事件响应能力。智慧管理系统则利用物联网技术,实现人员和设备的智能管理,提高校园运营效率。 智慧教学部分,方案提供了智慧学习系统和精品录播教室方案,支持专业级学习硬件和智能化网络管理,促进个性化学习和教学资源的高效利用。同时,教学质量评估中心和资源应用平台的建设,旨在提升教学评估的科学性和教育资源的共享性。 智慧环境建设则侧重于基于物联网的设备管理,通过智慧教室管理系统实现教室环境的智能控制和能效管理,打造绿色、节能的校园环境。电子班牌和校园信息发布系统的建设,将作为智慧校园的核心和入口,提供教务、一卡通、图书馆等系统的集成信息。 总体而言,智慧校园整体解决方案通过集成先进技术,不仅提升了校园的信息化水平,而且优化了教学和管理程,为学生、教师和家长提供了更加便捷、个性化的教育体验。
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值