零、目录html
I、网络流基础算法
II、网络流进阶之转换对偶图网络
III、网络流进阶之费用流优化
1、前言spa
本文为上一篇文章《网络流基础》之续集,一样3年前已有一篇文章讲解转换对偶图,这里再次为其翻新一次,但愿可以更好理解。code
2、最小割htm
讲网络流不得不提一个概念——最小割。便于理解,上一篇文章并无将其搅和进来。最小割是什么呢?如今要求割断部分路径上的流量,使从源点没有任何流量能够到达汇点,而截取的流量最小值即最小割。咱们再次拿出上次的模型:blog
首先从1至4最直接的20流量必然须要截掉;从1至2理应截取40,但因为2-3-4路径上的最大流仅为10,加上2-4流量为20,故只需截取30;总计50流量。队列
看着看着就以为有意思了——对于任意一条路径,其可以流通的流量最大值即是咱们须要割掉的流量最小值,即最大流=最小割。get
这里说起最小割的概念,可以更好的理解接下来的内容——转换对偶图。先看一道例题。
3、例题
首先,裸网络流的正确性毋庸置疑,此处再也不赘述,由于数据并无容许这种无脑的方式经过,因此咱们如今带着脑子想一个更好的办法。
4、转换对偶图
每一次狼的派遣其本质其割流。本题虽不是传统网格图,但一样能够看做一种特殊的三角网格图。目的一样是求最小割,咱们来先随手割一刀看是什么效果——
如图为一种非最小割方案,须要割掉33流量。咱们发现,当且仅当全部割线将图划分红两部分时,源点没有流量流向汇点。了解这一项规律后,知足最小割的方案能够轻易获得——将图中直接与汇点相连的三条边割掉,能够最小割为3 + 5 + 6 = 14,一样将图割成两部分。
如今,咱们可否将这张图进行必定改进——对于每一次割,能够理解为走过一条连通这两边两侧区域的边,其权值即流量,而咱们求的最小割即最小权值。因此如今咱们将原图转换一下——将每一块区域看做一点,两个区域之间的边看做两点之间相连的边,称之为对偶图。
看起来大功告成,不过咱们忽视了两个更大的区域。全部边界彷佛没法处理?这时咱们将全部左下方的边界与一个点视做相连,此点称之为超级源点;同理将右上方区域视做超级汇点。
这样咱们每一次求最小割,其实本质是在新的对偶图上跑最短路,起点为超级源点,终点为超级汇点。综上,上述方案即为:
5、代码
要注意的是,N/M <=1000的条件下,其对偶图最多会存在6*10^6个节点,Dijkstra算法时间复杂度为O(n^2),显然没法经过;起初我尝试了SPFA算法,但因为其复杂度的不稳定,第10个点TLE了;最终修改为了优先队列优化的Dijkstra算法,其复杂度为O(nlogn)。
#include <cstdio> #include <cstring> #include <vector> #include <queue> using namespace std; #define MAXN 6000005 #define INF 0x3f3f3f3f int n, m, t, h[MAXN], dis[MAXN], w, o; struct Edge { int v, next, w; } e[MAXN]; struct Node { int n, w; }; struct cmp { bool operator () (Node a, Node b) { return a.w > b.w; } }; priority_queue <Node, vector<Node>, cmp> Q; void add(int u, int v, int w) { o++, e[o] = (Edge) {v, h[u], w}, h[u] = o; o++, e[o] = (Edge) {u, h[v], w}, h[v] = o; } void init() { scanf("%d %d", &n, &m), t = (n - 1) * (m - 1) * 2 + 1; memset(h, -1, sizeof(h)), memset(dis, INF, sizeof(dis)); for (int i = 1, tot = 2; i <= n; i++) for (int j = 1; j <= m - 1; j++, tot += 2) { int u = i == 1 ? t : tot - m * 2 + 1, v = i == n ? 0 : tot; scanf("%d", &w), add(u, v, w); } for (int i = 1, tot = 1; i <= n - 1; i++) { for (int j = 1; j <= m - 1; j++, tot += 2) { int u = j == 1 ? 0 : tot - 1; scanf("%d", &w), add(u, tot, w); } scanf("%d", &w), add(tot - 1, t, w); } for (int i = 1, tot = 1; i <= n - 1; i++) for (int j = 1; j <= m - 1; j++, tot += 2) scanf("%d", &w), add(tot, tot + 1, w); } void work() { Q.push((Node) {0, 0}), dis[0] = 0; while (!Q.empty()) { Node o = Q.top(); for (int x = h[o.n]; x != -1; x = e[x].next) { int v = e[x].v; if (dis[v] > dis[o.n] + e[x].w) dis[v] = dis[o.n] + e[x].w, Q.push((Node) {v, dis[v]}); } Q.pop(); } } int main() { init(); work(); printf("%d", dis[t]); return 0; }