[知识点]网络流进阶之转换对偶图

零、目录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;
} 
相关文章
相关标签/搜索