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

零、目录

  I、网络流基础

  II、网络流进阶之转换对偶图

  III、网络流进阶之费用流

 

一、前言

本文为上一篇文章《网络流基础》之续集,同样3年前已有一篇文章讲解转换对偶图,这里再次为其翻新一次,希望能够更好理解。

 

二、最小割

讲网络流不得不提一个概念——最小割。便于理解,上一篇文章并没有将其搅和进来。最小割是什么呢?现在要求割断部分路径上的流量,使从源点没有任何流量可以到达汇点,而截取的流量最小值即最小割。我们再次拿出上次的模型:

首先从1至4最直接的20流量必然需要截掉;从1至2理应截取40,但由于2-3-4路径上的最大流仅为10,加上2-4流量为20,故只需截取30;总计50流量。

看着看着就觉得有意思了——对于任意一条路径,其能够流通的流量最大值便是我们需要割掉的流量最小值,即最大流=最小割

这里提及最小割的概念,能够更好的理解接下来的内容——转换对偶图。先看一道例题。

 

三、例题

首先,裸网络流的正确性毋庸置疑,此处不再赘述,因为数据并没有允许这种无脑的方式通过,所以我们现在带着脑子想一个更好的办法。

 

四、转换对偶图

每一次狼的派遣其本质其割流。本题虽不是传统网格图,但同样可以看作一种特殊的三角网格图。目的同样是求最小割,我们来先随手割一刀看是什么效果——

如图为一种非最小割方案,需要割掉33流量。我们发现,当且仅当所有割线将图划分成两部分时,源点没有流量流向汇点。了解这一项规律后,满足最小割的方案可以轻易得到——将图中直接与汇点相连的三条边割掉,可以最小割为3 + 5 + 6 = 14,同样将图割成两部分。

现在,我们能否将这张图进行一定改进——对于每一次割,可以理解为走过一条连通这两边两侧区域的边,其权值即流量,而我们求的最小割即最小权值。所以现在我们将原图转换一下——将每一块区域看作一点,两个区域之间的边看作两点之间相连的边,称之为对偶图

 看起来大功告成,不过我们忽视了两个更大的区域。所有边界似乎无法处理?这时我们将所有左下方的边界与一个点视作相连,此点称之为超级源点;同理将右上方区域视作超级汇点。

 

这样我们每一次求最小割,其实本质是在新的对偶图上跑最短路,起点为超级源点,终点为超级汇点。综上,上述方案即为:

 

五、代码

要注意的是,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;
} 

 

posted @ 2018-08-17 21:11  jinkun113  阅读(1176)  评论(0编辑  收藏  举报