推荐一下汪峰的《向阳花》,看着100期战报,听着,就爱上了。
Statement:
我是弱X...讲的东西很弱...杂鱼求放过啊...
1.What‘s Network Flow
网络流就是在一个网络上流嘛..
相当于一个限制水流向的水管系统。问从源点到汇点的最大水流速。
2.最大流
算法:FF、EK、HLPP、ISAP、Dinic
他们是什么?
FF不知道。从EK说起。
EK:基础的最大流算法,每次Bfs寻找最短路进行增广,这时候的增广和匈牙利算法的增广不同,找出一条残余路径就可以了。
HLPP:使用了距离标号。理论复杂度最优。O(n^2 * sqrt(m)), 但常常跑不过sap,代码(据说)冗长。与增广路算法思想不怎么相同。
从EK到Dinic:引入HLPP的距离标号。每次只增广两端点距离标号差为一的边。
从Dinic到isap:
一群人闲着无聊,觉得dinic太慢了,就加上了重标号技术,使得算法无须在每次增广后再进行BFS每个顶点进行距离标号,
这种主动标号技术使得修正后算法的速度有了不少提高。但这点提高是不足称道的。
人们又发现当某个标号的值没有对应的顶点后,即增广路被截断了,于是算法便可以提前结束,这种启发式的优化称为Gap优化。
最后人们结合了连续增广,分层图,多路增广,Gap优化,主动标号等穷凶极恶的优化,更甚者在此之上狂搞个手动递归,
于是产生了增广路算法的高效算法–ISAP算法。所以推荐isap,当然dinic已经足够应付大多数考试。
模板:
sap(非递归)
(能再漂亮点么【喂..自重....】)(事实证明不但好看还有用..强虐标程)
(比牛牛的Dinic还短 http://blog.csdn.net/jerrydung/article/details/8149652)
#include<cstdio>
#include<cstring>
#define adde(a, b, c) ({*(++eptr) = (edge){b, c, h[a], eptr + 1}, h[a] = eptr;\
*(++eptr) = (edge){a, 0, h[b], eptr - 1}, h[b] = eptr;})
#define oo 0x3f3f3f3f
#define ot "%d"
#define maxe 610005
#define maxn 310005
#define ms(a, b) memset((a), (b), sizeof (a))
using namespace std;
struct edge
{
int t, v;
edge *nt, *rev;
}eg[maxe], *ed[maxe], *h[maxe], *pre[maxn], *now[maxn], *eptr;
int n, m, dis[maxn], gap[maxn], que[maxn], head, tail, s, t;
void bfs()
{
dis[t] = 0, gap[0] = 1;
head = 0, tail = 1, que[1] = t;
while(head != tail)
{
int p = que[++head];
for (edge *e = h[p]; e; e = e -> nt)
if (!e -> v && (dis[e -> t] > dis[p] + 1))
++gap[dis[que[++tail] = e -> t] = dis[p] + 1];
}
}
int sap(int num)
{
int u = s, v, w, flow, ans = 0;
ms(dis, 0x3f); ms(gap, 0); bfs();
while (dis[s] < n)
{
for (bool flag = 1; flag;)
{
flag = 0; if (!now[u]) now[u] = h[u];
for (edge *e = now[u]; e && !flag; e = e -> nt)
if (e -> v && dis[v = e -> t] == dis[u] - 1)
{
now[u] = e; pre[u = v] = e; flag = 1;
if (u == t)
{
flow = oo;
for (int i = u; i != s; i = pre[i] -> rev -> t)
if (flow > pre[i] -> v) flow = pre[i] -> v;
for (edge *d; u != s; u = d -> rev -> t)
d = pre[u], d -> v -= flow, d -> rev -> v += flow;
ans += flow; while (now[u] -> v) u = now[u] -> t;
}
}
}
w = n; if (!(--gap[dis[u]])) break;
for (edge *e = now[u] = h[u]; e; e = e -> nt)
if (e -> v) if (w > dis[e -> t]) w = dis[e -> t];
++gap[dis[u] = w + 1]; if (u != s) u = pre[u] -> rev -> t;
}
return ans;
}
int main()
{
freopen("maxflow.in","r",stdin);
freopen("maxflow.out","w",stdout);
scanf(ot ot, &n, &m);
ms(h, 0); int a = 0, b = 0, c = 0; eptr = eg;
for (int i = 1; i <= m; ++i) scanf(ot ot ot, &a, &b, &c), adde(a, b, c);
s = 1; t = n;
printf(ot, sap(n));
return 0;
}
另附最大流强数据_YSQ : http://pan.baidu.com/share/link?shareid=114983&uk=3491581918
3. 费用流
最大流的基础上费用最小。
算法:zkw,暴力spfa,消圈,Primal-Duel(听起来很厉害?其实就是spfa多路增广)推荐二分图/小边权图 zkw, 其他PD
详见http://www.artofproblemsolving.com/blog/54262
Primal-Duel模板(经典费用流napkin):(zkw的先欠着...)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<climits>
#define ot "%d"
#define kg " "
#define kh "\n"
#define otl "%I64d"
#define max(a, b) ({int _ = (a), __ = (b); _ > __ ? _ : __;})
#define min(a, b) ({int _ = (a), __ = (b); _ < __ ? _ : __;})
#define swap(a, b) ({int _ = (a); (a) = (b); (b) = _;})
#define maxn 2002
#define maxm 120002
#define oo 0x3f3f3f3f
#define ms(a, b) memset(a, b, sizeof a)
#define adde(a, b, c, d) {*(++eptr) = (edge){b, c, d, eptr + 1, h[a]}, h[a] = eptr;\
*(++eptr) = (edge){a, 0, -d, eptr - 1, h[b]}, h[b] = eptr;}
struct edge
{
int t, v, c;
edge *rev, *nt;
}eg[maxm], *ed[maxm], *eptr, *h[maxn];
using namespace std;
int n, p, m, a, fa, b, fb, s, t, r, cost, tot;
int head, tail, que[maxn * 5 + 5], dis[maxn];
bool vis[maxn];
void init()
{
freopen("napk.in", "r", stdin);
freopen("napk.out", "w", stdout); eptr = eg;
scanf(ot ot ot ot ot ot, &n, &p, &a, &fa, &b, &fb);
ms(h, 0); s = (n << 1) + 1; t = (n << 1) + 2;
for (int i = 1; i <= n; ++i)
{
scanf(ot, &r);
adde(s , i, r, 0);
adde(s , i + n, oo, p);
adde(i + n, t, r, 0);
}
for (int i = n - 1; i; --i) adde(i, i + 1, oo, 0);
for (int i = n - a; i; --i) adde(i, n + i + a, oo, fa);
for (int i = n - b; i; --i) adde(i, n + i + b, oo, fb);
}
bool labelit()
{
ms(dis, oo);
dis[t] = head = tail = 0; que[++tail] = t;
while (head != tail)
{
int p = que[++head], q;
for (edge *e = h[p]; e; e = e -> nt)
if (e -> rev -> v && (q = dis[p] - e -> c) < dis[e -> t])
dis[que[++tail] = e -> t] = q;//Special attention paid (Via DRJ)
}
if (dis[s] >= oo) return 0;
for (int i = 1; i <= t; ++i)
for (edge *e = h[i]; e; e = e -> nt)
e -> c += dis[e -> t] - dis[i];
return tot += dis[s], 1;
}
int work(int k, int flow)
{
if (k == t) return cost += flow * tot, flow;
vis[k] = 1; int tmp = flow;
for (edge *e = h[k]; e; e = e -> nt)
if (e -> v && !e -> c && !vis[e -> t])
{
int p = work(e -> t, tmp < e -> v ? tmp : e -> v);
tmp -= p; e -> v -= p; e -> rev -> v += p;
if (!tmp) return flow;
}
return flow - tmp;
}
int mcf()
{
tot = cost = 0;
while (labelit())
{
do
ms(vis, 0);
while(work(s, oo));
}
return cost;
}
int main()
{
init();
printf(ot, mcf()); //Minimum-cost flow
return 0;
}
![](https://img-my.csdn.net/uploads/201211/07/1352298218_8787.png)
4.上下界网络流
对于一些边,限定其流量上界和下界。
解法:详见《一种简易的方法求解流量有上下界的网络 》 还有 《最大流在信息学竞赛中应用的一个模型》
模板:懒得写了...
另外把基哥的一句话小结各种网络流蒯过来:(原文:http://blog.csdn.net/pouy94/article/details/6628521)
(我就不说qw半年前就在看了...)
最大流:DINIC or SAP
最小费用最大流:SPFA+增广(费用的值较离散) or ZKW(费用的值集中)
有源汇的上下界最大流:新建s', t',用(i, j, l, r)表示i到j有一条下界为l上界为r的边,将每条这样的边拆成(s', j, 0, l), (i, t', 0, l), (i, j, 0, r-l),加入边(t, s, 0, max)再从s‘到t'求最大流,再去掉(t, s, 0, max)这条边,从s到t求最大流
有源汇的上下界最小可行流:基本同上,将最后一步改成从t到s反求一遍最大流来退流(注意可能大于可行流);也可以二分(t, s, 0, max)这条边的容量(比较慢...)
有源汇的上下界最小费用可行流:拆边方法同上,从s'向t'求一遍最小费用最大流
有源汇的上下界最小费用最大流:基本同上,还要再s向t求一遍最小费用最大流(*这个是自己YY的,可能不对,求指教)
无源汇的最大流(无向图全局最小割):Stoer-Wagner算法
无源汇的所有点对间最大流:分治,在当前点集内随便选两个点求最小割,用这个割更新一遍所有跨在两边的点对(不一定只是当前点集内的点),再将自己的点集割成两部分,递归做
无源汇的上下界可行流:拆边,直接从s'到t'跑一遍最大流
无源汇的上下界最小费用可行流:拆边,直接从s'到t'跑一遍最小费用最大流
平面图最小割转最短路:将平面区域当成点,两个点之间的边权为原来这两个平面区域之间的边的容量,补上一条汇到源的正无穷边之后,求这条正无穷边的一边到另一边的最短路
基本上就是这样了。
另外新坑预定:Ver0.2(构图篇)