让菜鸡讲一讲网络流(isap)

让我先讲一个故事吧。

一些小精灵要准备从银月城(S)迁徙到Nibel山(T)。

这两个地方之间的道路构成了一个网络。

每个道路都有它自己的容量,这决定了每天有多少小精灵可以同时从这儿通过。

现在它们想知道,它们迁徙的速度最大是多少只每天。

这就是一道红果果的最大流问题。



在建图时,我们把每条边拆成2条,

它们方向相反,和原来那条边方向相同的边的容量还是原来的容量,

而另一条边的容量就设成0。

当我们要修改剩余容量的时候,

把正方向的边的容量减少,把反方向的边的容量增加,

就可以很方便的修改它了。



一种最朴实的算法是,

每次寻找一条可以从S到T的有可用容量剩余的路径(我们把它叫增广路,虽然我也不知道为什么是这个名字),

不断寻找到没有这种路径为止。

这种算法叫EK,复杂度是\(O(fm)\),f是得到的最大流,m是道路数量。

可见这个算法运行速度很垃圾,特别时遇到这种情况

              /> 次元壁1
 (999/999) __/     |     \__ (999/999)
          /        |        \>
    银月城        (1/1)         Nibel山
          \__      |      __/>
 (999/999)   \     v     /   (999/999)
              \> 次元壁2

寻找的增广路会一直穿过次元壁1和次元壁2来回走动

每次都只找到一条可以通过1只精灵的路

卡成gou bi



因此,我们提出了一个改进的算法:

把每一个点到起点S的距离用BFS求出来,得到一个分层图,

然后在寻找增广路的时候,限制当前点只能往比它距离刚刚好大1的点前进,

在找到一条增广路后,再用BFS更新分层图,继续寻找增广路,肛到你听到为止直到找不到为止。

我们把这个算法叫做Dinic,复杂度是\(O(min(Xuanxue,n^2 \times m))\),n是点数。不要问我Xuanxue是什么鬼

这个算法有一个优化,叫GAP优化,
主要就是把离起点S等于某个距离的点的数量记下来,
如果离起点S的距离等于某值的点全都不见了,
那么这个图就发生了断层,
再也找不到一条增广路出来。
这个时候就阔以提前结束程序哒。
有时候可以为程序提速100倍以上


有些人啊,发现这个狄尼克dinic算法有个大问题,

就是它每一次BFS的意义好像并不大,

毕竟出现改动的道路就那么点。

于是这些人就搞出了一个更快的算法:

isap

复杂度也许是\(O(0.8*(n^2 \times m))\)

和dinic最大的不同就是,它可以在寻找增广路的同时,自己更新分层图。

主要就是在找不到合法的增广路的时候(比如没有满足距离刚刚好大1的点),

就把现在这个点的距离设为离自己最近的出边的点的距离+1。

这里还有个优化叫当前弧优化,不过意义不大。

这是网上某个isap代码

int source;         // 源点
int sink;           // 汇点
int p[max_nodes];   // 可增广路上的上一条弧的编号
int num[max_nodes]; // 和 t 的最短距离等于 i 的节点数量
int cur[max_nodes]; // 当前弧下标
int d[max_nodes];   // 残量网络中节点 i 到汇点 t 的最短距离
bool visited[max_nodes];

// 预处理, 反向 BFS 构造 d 数组
bool bfs()
{
    memset(visited, 0, sizeof(visited));
    queue<int> Q;
    Q.push(sink);
    visited[sink] = 1;
    d[sink] = 0;
    while (!Q.empty())
    {
        int u = Q.front();
        Q.pop();
        for (iterator_t ix = G[u].begin(); ix != G[u].end(); ++ix)
        {
            Edge &e = edges[(*ix)^1];
            if (!visited[e.from] && e.capacity > e.flow)
            {
                visited[e.from] = true;
                d[e.from] = d[u] + 1;
                Q.push(e.from);
            }
        }
    }
    return visited[source];
}

// 增广
int augment()
{
    int u = sink, df = __inf;
    // 从汇点到源点通过 p 追踪增广路径, df 为一路上最小的残量
    while (u != source)
    {
        Edge &e = edges[p[u]];
        df = min(df, e.capacity - e.flow);
        u = edges[p[u]].from;
    }
    u = sink;
    // 从汇点到源点更新流量
    while (u != source)
    {
        edges[p[u]].flow += df;
        edges[p[u]^1].flow -= df;
        u = edges[p[u]].from;
    }
    return df;
}

int max_flow()
{
    int flow = 0;
    bfs();
    memset(num, 0, sizeof(num));
    for (int i = 0; i < num_nodes; i++) num[d[i]]++;
    int u = source;
    memset(cur, 0, sizeof(cur));
    while (d[source] < num_nodes)
    {
        if (u == sink) {
            flow += augment();
            u = source;
        }
        bool advanced = false;
        for (int i = cur[u]; i < G[u].size(); i++)
        {
            Edge& e = edges[G[u][i]];
            if (e.capacity > e.flow && d[u] == d[e.to] + 1)
            {
                advanced = true;
                p[e.to] = G[u][i];
                cur[u] = i;
                u = e.to;
                break;
            }
        }
        if (!advanced)
        { // retreat
            int m = num_nodes - 1;
            for (iterator_t ix = G[u].begin(); ix != G[u].end(); ++ix)
                if (edges[*ix].capacity > edges[*ix].flow)
                    m = min(m, d[edges[*ix].to]);
            if (--num[d[u]] == 0) break; // gap 优化
            num[d[u] = m+1]++;
            cur[u] = 0;
            if (u != source)
                u = edges[p[u]].from;
        }
    }
    return flow;
}

看起来isap代码量很大。

其实不然,isap可以写的很简单。

这是我的这一题的全部代码

#include<bits/stdc++.h>
using namespace std;
inline int gotcha()
{
    register int a=0,b=1,c=getchar();
    while(!isdigit(c))b^=c=='-',c=getchar();
    while(isdigit(c))a=a*10+c-48,c=getchar();
    return b?a:-a;
}
const int _ = 10002 , __ = 200002;
int to[__],ne[__],v[__],he[_]={0},ecnt=1;
void adde(int a,int b,int c){to[++ecnt]=b,v[ecnt]=c,ne[ecnt]=he[a],he[a]=ecnt;}
int n,m,S,T,dis[_],gap[_];
int dfs(int d,int flw)
{
    if(d==T || flw==0)return flw;
    int i,g,mid=n-1,los=flw;
    for(i=he[d];i;i=ne[i])
        if(v[i]>0)
        {
            if(dis[d]==dis[to[i]]+1)
            {
                g=dfs(to[i],min(los,v[i])),v[i]-=g,v[i^1]+=g,los-=g;
                if(dis[S]>=n)return flw-los;if(!los)break;
            }
            mid=min(mid,dis[to[i]]);
        }
    if(flw==los){if(--gap[dis[d]]==0)dis[S]=n;dis[d]=mid+1,gap[dis[d]]++;}
    return flw-los;
}
int isap(){int ans=0;gap[S]=n;while(dis[S]<n)ans+=dfs(S,1e9);return ans;}
int main()
{
    register int i,j,k,a;
    n=gotcha(),m=gotcha(),S=gotcha(),T=gotcha();
    for(i=1;i<=m;i++)j=gotcha(),k=gotcha(),a=gotcha(),adde(j,k,a),adde(k,j,0);
    printf("%d",isap());
    return 0;
}

我突然有个写伪代码的冲动

要不我就写在这儿吧,还可以增强记忆

大法师的工作
信息:
    现在在哪儿
    现在带领了多少精灵

如果我现在已经到了Nibel山,或者我这里已经没有更多的精灵了,
    那么我就把现在我这儿的精灵数量汇报回去。

现在我要记录这儿离银月城的可能的最短距离,以及我还有多少精灵还滞留在这儿。

我要寻找可以走的道路。
    如果这条路还有精灵的容身之地,
    并且这条路的终点离我们这儿的距离差是1的话,
        我将派出一个大法师信使,
            让他去这条路的终点,
            而她带领精灵的数量是,这条路的剩余容量与我这儿还滞留的精灵数量的最小值,毕竟带多了没有用。
        等她把成功到达终点的精灵数量带回来,
        我就把仍滞留在这儿的精灵的数量记录一下,
        也把这条道路的容量修改一下。
        当我收到了紧急的停止通知(GAP优化),
            我将立刻把成功前往目的地的精灵的数量汇报回去。
        如果我这儿已经没有滞留的精灵了,
            那我就不用寻找道路了。

    除此之外,我还要更新这儿离银月城的可能的最短距离。

当我没有把任何精灵送到Nibel山,我会考虑这儿的距离是不是有点问题。
    我会将这里从距离统计的计数君中抹除,
        如果已经没有和这儿距离一样的点,
            那么就散布紧急通知(GAP优化)。
    之后把这里的距离改成可能的最短距离,并且让计数君把这里加入距离统计中。

最后,我将汇报从我这里成功到达Nibel山的精灵的数量。

领主伊萨普的工作
没有信息

我将不断地
    派遣大法师,
        让她带上许多的精灵,
        从银月城出发,
    统计成功到达Nibel山的精灵的数量,
直到收到紧急的停止通知(GAP优化)为止。

原文地址:https://www.cnblogs.com/finder-iot/p/8408868.html

时间: 2024-11-14 12:31:50

让菜鸡讲一讲网络流(isap)的相关文章

菜鸡程序猿的开始:java基础知识之一个简单ATM机

import java.util.Scanner; public class Atm{ static int allmoney=150000; //ATM现有余额 static int all=200000; // ATM最大量 static int money =10000; // 初始化用户的余额 public static void main(String[] args) { System.out.print("*********************************"

前端菜鸡关于 JS,Ajax,JSON,API,的一些思绪整理

header: 这是我的第一篇博客,希望这篇菜鸡总结能帮我找回该努力的方向吧.也许还能帮到几个和我境遇类似的大学狗?反正我现在是觉得这篇东西除了我不可能有别人会看了.hhhh... body: /*  7月29号加入了FreeCodeCamp学前端.大概倒推三个月在codecadymy学过python和JS的基础语法.倒推年初还把python和后端当做自己努力的方向.在知乎上听从建议去啃SCIP,结果第二章看到一半实在是被自己的数学打败了.又忙着考试和打游戏,大二的下学期就糊里糊涂的过去了.大学

渣渣菜鸡的蚂蚁金服面试经历(一)

蚂蚁金服 电话一面 1.自我介绍.自己做的项目和技术领域 2.项目中的监控:那个监控指标常见的哪些? 3.微服务涉及到的技术以及需要注意的问题有哪些? 4.注册中心你了解了哪些? 5.consul 的可靠性你了解吗? 6.consul 的机制你有没有具体深入过?有没有和其他的注册中心对比过? 7.项目用 Spring 比较多,有没有了解 Spring 的原理?AOP 和 IOC 的原理 8.Spring Boot除了自动配置,相比传统的 Spring 有什么其他的区别? 9.Spring Clo

菜鸡的入门史

这篇博客记录我是怎么误打误撞来到了编程世界,以及为什么决定以此为业,一方面作为博客输出,另一方面希望能给需要的同学当一篇经验贴参考. 发现格式有点问题,先将就看一下,学习了再调整. 目录: 一.个人背景介绍 二.第一次接触前端 三.转向后端 Java 四.总结 五.参考建议 一.个人背景介绍 既然作为参考,那么肯定是要全方位讲清楚的,尤其是个人背景,不谈个人基础背景的经验都是耍流氓,个人情况不一致,适合自己的不一定适合别人,有背景情况下可以大概明白误差,因此该经验仅供考,盲目复制可能引发不适.

菜鸡问offer所在何方

大三的时候就想写博客了,结果拖到研二快结束才开通博客,这拖延症也是没谁了!!! 真正刺激到我的是,最近找实习,辛苦了一个月一个offer也没有,感觉到作为菜鸡的无奈---互联网寒潮,菜鸡水里泡,大佬岸上笑. 这段时间每天都很焦虑,晚上睡的不踏实,总在思考未来,思考有没有未来. 将死之人总会回顾往事,绝望的人也是如此.回顾这两年的研究生生活,找不出丝毫亮点,丝毫有意义之处.本科学的软件工程,研究生期间基本上在推那几个公式,代码都没怎么写,真的是丢了自己的老本行,导致我现在甚至对编程感到一种很强的生

讲一讲什么叫阻塞非阻塞同步异步

1.讲一讲什么叫阻塞非阻塞同步异步全是用来形容方法的,形容一个方法返回值状态的. 2.io读取,网络读取,jdbc读取,这些流的操作都是bio的,都是阻塞的. 3.所以沃恩一般在处理io操作时,都采用多线程来提高bio的效率. 4.io操作,就是本地文件,网络,数据嘛嘛.所以在这三种读取数据时,都要采用多线程提高效率. 5.多线程处理阻塞方法时,只不过是避免了主线程的阻塞,但是让子线程,也就是处理每个http request的线程去发生阻塞了. 6.传统的古老的开发方式: 单线程执行阻塞方法->

一只菜鸡的话

大家好,我是Parallels,一只大二的菜鸡,在大一上只学过C语言,大一下自学过一点C++,对于算法竞赛几乎是0基础,但是却对编程及算法有很浓的厚兴趣,也很想参加ACM,向我们学校的大牛一样在ICPC区域赛的舞台上一展身手,所以还需要付出很大的努力,不过我愿意付出,所以,在此先给自己定一个小目标:两年内成为CF红名,在大三大四能代表学校去打ICPC区域赛并拿奖,希望我在大四毕业的时候能交上一份完美的答卷. 对此,尽管大二学习任务很重,要做的事情也很多,但是如果每天抽出点时间学一个算法,做一道题

Html菜鸡大杂烩

<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>菜鸡大杂烩</title> </head> <body> <p>注册帐号</p> <hr/> <form> 邮箱帐号 <input type="text"/&g

菜鸡的最后挣扎

免不了吐槽,那就先来吐槽一下自己选择这个编译器的过程吧. 第二次作业算是我的一个对自己不刻苦不奋进的一个惩罚吧. 原本打算的是用vs2015,因为这个软件可以用我还会那么一点点的C和c++编程,所以我开始的打算是安装这个软件.但是经过一系列的搜索,还是这么大的软件,昨天花了一晚上让我去下载,今天中午安装的时候又是让我失望,不仅安装的语言都是英文,而且还有一个secondary installer ,要离线安装.结果跟团队的队友商量了一下,正好他们要用eclipse来编程,用java,最后思索了半

【自编题】震惊!某菜鸡竟公然出题黑自己!

有巨佬有兴趣做一下吗?非常水的题. 手残的stg之路(gzz.cpp/pas) 题目背景 有一天,菜鸡手残zx在玩绀珠传E.当她打到123的4符:梦符「刈安色的迷梦」时,由于她太手残,怎么转圈也扭不过去.无奈之下,她放了一个b.但是从这之后她好像被123缠上了,连数学考试都考了123分.为了避免被zn骂,也为了不再被123纠缠下去,她立志要扭过这张符,用实力证明自己! -- -- -- -- 但是作为天下第一手残,她发现自己真的没法凭借自己的力量扭过去.于是她打算用计算机编程解决此问题. 题目描