让我先讲一个故事吧。

一些小精灵要准备从银月城(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;
}

我在这儿省去了累赘的BFS和retreat操作,

把它们简化到dfs中。

实测运行速度依然很快

且程序关键部分(链式前向星,dfs(),isap())只有约0.6KB

性价比极高啊


以下是博主抽风内容


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

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

大法师的工作
信息:
现在在哪儿
现在带领了多少精灵 如果我现在已经到了Nibel山,或者我这里已经没有更多的精灵了,
那么我就把现在我这儿的精灵数量汇报回去。 现在我要记录这儿离银月城的可能的最短距离,以及我还有多少精灵还滞留在这儿。 我要寻找可以走的道路。
如果这条路还有精灵的容身之地,
并且这条路的终点离我们这儿的距离差是1的话,
我将派出一个大法师信使,
让她去这条路的终点,
而她带领精灵的数量是,这条路的剩余容量与我这儿还滞留的精灵数量的最小值,毕竟带多了没有用。
等她把成功到达终点的精灵数量带回来,
我就把仍滞留在这儿的精灵的数量记录一下,
也把这条道路的容量修改一下。
当我收到了紧急的停止通知(GAP优化),
我将立刻把成功前往目的地的精灵的数量汇报回去。
如果我这儿已经没有滞留的精灵了,
那我就不用寻找道路了。 除此之外,我还要更新这儿离银月城的可能的最短距离。 当我没有把任何精灵送到Nibel山,我会考虑这儿的距离是不是有点问题。
我会将这里从距离统计的计数君中抹除,
如果已经没有和这儿距离一样的点,
那么就散布紧急通知(GAP优化)。
之后把这里的距离改成可能的最短距离,并且让计数君把这里加入距离统计中。 最后,我将汇报从我这里成功到达Nibel山的精灵的数量。 领主伊萨普的工作
没有信息 我将不断地
派遣大法师,
让她带上许多的精灵,
从银月城出发,
统计成功到达Nibel山的精灵的数量,
直到收到紧急的停止通知(GAP优化)为止。

upd:做了狼抓兔子之后还是决定把bfs代码放上来

queue<int> q;
void bfs()
{
memset(dis,-64,sizeof(dis)),memset(gap,0,sizeof(gap));
while(!q.empty())q.pop();
dis[T]=1,gap[1]=1,q.push(T);
int i,a,b;
while(!q.empty())
{
a=q.front(),q.pop();
for(i=he[a];i;i=ne[i])
if(dis[to[i]]<0)b=to[i],dis[b]=dis[a]+1,gap[dis[b]]++,q.push(b);
}
}

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

  1. HDU 2064 菜鸡第一次写博客

    果然集训就是学长学姐天天传授水铜的动态规划和搜索,今天讲DP由于困意加上面瘫学长"听不懂就是你不行"的呵呵传授,全程梦游.最后面对连入门都算不上的几道动态规划,我的内心一片宁静,甚 ...

  2. 一个数学不好的菜鸡的快速沃尔什变换(FWT)学习笔记

    一个数学不好的菜鸡的快速沃尔什变换(FWT)学习笔记 曾经某个下午我以为我会了FWT,结果现在一丁点也想不起来了--看来"学"完新东西不经常做题不写博客,就白学了 = = 我没啥智 ...

  3. 渣渣菜鸡的 ElasticSearch 源码解析 —— 启动流程(下)

    关注我 转载请务必注明原创地址为:http://www.54tianzhisheng.cn/2018/08/12/es-code03/ 前提 上篇文章写完了 ES 流程启动的一部分,main 方法都入 ...

  4. 渣渣菜鸡的 ElasticSearch 源码解析 —— 启动流程(上)

    关注我 转载请务必注明原创地址为:http://www.54tianzhisheng.cn/2018/08/11/es-code02/ 前提 上篇文章写了 ElasticSearch 源码解析 -- ...

  5. Html菜鸡大杂烩

    <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...

  6. 菜鸡谈OO 第二单元总结

    “欢迎来到(玄学)多线程的新世界” Homework1 单部傻瓜电梯调度 Part1 多线程设计策略 第一次学到了线程这个概念,与之前的编程体验大有不同.最大的区别在于从原本的线性发生程序变成了多个行 ...

  7. 菜鸡谈OO 第一单元总结

    “OOP永远是我的好朋友爸爸!” ——来自某无能狂怒的菜鸡 身处在OO的第一个摸鱼黄金周中的我,感觉到了巨大的满足感.如果写博客这种充满意义的事情可以代替我们亲爱的作业,那么我提议每周来两个:)下面开 ...

  8. 经典面试题|讲一讲JVM的组成

    JVM(Java 虚拟机)算是面试必问的问题的了,而但凡问 JVM 一定会问的第一个问题就是:讲一讲 JVM 的组成?那本文就注重讲一下 JVM 的组成. 首先来说 JVM 的组成分为,整体组成部分和 ...

  9. ACM菜鸡退役帖——ACM究竟给了我什么?

    这个ACM退役帖,诸多原因(一言难尽...),终于决定在我大三下学期开始的时候写出来.下面说两个重要的原因. 其一是觉得菜鸡的ACM之旅没人会看的,但是新学期开始了,总结一下,只为了更好的出发吧. 其 ...

  10. JAVA经典面试题:讲一讲JVM的组成

    JVM(Java 虚拟机)算是面试必问的问题的了,而但凡问 JVM 一定会问的第一个问题就是:讲一讲 JVM 的组成?那本文就注重讲一下 JVM 的组成. 首先来说 JVM 的组成分为,整体组成部分和 ...

随机推荐

  1. ES7的Async/Await的简单理解

    Async/Await 的个人见解 正文: async,顾名思义,一个异步执行的功能,而 await 则是配合 async 使用的另一个关键字,也是闻字识其意,就是叫你等待啦! 二者配合食用效果更佳哦 ...

  2. COGS 2091. Asm.Def的打击序列

    ★★★   输入文件:asm_lis.in   输出文件:asm_lis.out   简单对比时间限制:4 s   内存限制:256 MB [题目描述] 白色圆柱形的“蓝翔”号在虚空中逐渐变大,一声沉 ...

  3. MySQL入门很简单: 5 索引

    1. 索引的含义和特点 索引:创建在表上,是对数据库表中一列或多列的值进行排序的一种结构. 存储类型: B性树(BTREE)索引和哈希(HASH)索引: InnoDB和MyISAM支持BTREE索引, ...

  4. SQL:获取语句执行时间2

    获取sql执行时间方法2 --清除缓存 CHECKPOINT; DBCC DROPCLEANBUFFERS; DBCC FREEPROCCACHE; DBCC FREESYSTEMCACHE ('AL ...

  5. eclipse快捷键(个人经常使用的)

    周末闲来无聊,就把一直以来eclipse使用的快捷键梳理了下,希望对有需要的朋友有所帮助,绝对原创! 1.快速复制一行或者多行 首先选中你要复制的行,如果是一行,则鼠标焦点在那一行即可,如果是多行,可 ...

  6. 修改Windows下的Memcached下的服务端口

    在命令模式下,可以使用 memcached -p 12000 去指定端口,可服务安装后,却总是只有 -d runservice 的参数. 通过修改注册表来达到这个修改端口的目的. 在 HKEY_LOC ...

  7. mingw libgcc_s_sjlj-1.dll is missing

    习惯了在linux环境下工作,编译wingdows平台程序采用mingw工具.编译完,运行exe程序,弹出错误信息: libgcc_s_sjlj-1.dll is missing 百度了一下,原来是编 ...

  8. <strong>和 <b> 的区别

    前几天,看到这样的一个笑话:甲:“我精通前端开发”,乙:“strong和b的区别是什么?” 甲:.... 其实我也搞不清有什么区别,因此我整理了一下: 一.为什么会有这样一个问题 我们在一个没有附加式 ...

  9. PHP中的生成XML文件的4种方法分享

    生成如下XML串 Xml代码 <?xml version="1.0" encoding="utf-8"?> <article> < ...

  10. 绕不开的this

    犹豫两秒要不要整理this,从红皮书上看了半天,没搞懂哎(弱爆了) 什么是this?this是在执行上下文创建时期创建的一个执行过程中不可改变的变量.执行上下文是指js引擎会将代码执行前需要的变量th ...