【网络流入门-最大流算法】知识点讲解

网络流的一些基本名词概念

弧的容量:指的是一条弧(有向边)最大承受能力。

弧的流量:实际通过这条弧的流量。

(流量和容量的概念一定要区分开。之后我们画的图里弧上的那个数字,指的是容量!!)

网络流:所有弧上流量的集合。

源点:网络指定的起始点

汇点:网络指定的终止点

可行流:简单的说就是一张图能够实现的网络流。(满足弧流量限制条件(小于等于弧容量)和平衡条件(源点的净流出量为总流量,汇点的净流入量为总流量))

可行流的流量:能够实现的网络流的流量。

最大流(网络最大流):流量最大的可行流。

零流:每条弧的流量都为零。

伪流(容量可行流):满足弧流量限制条件,不满足平衡条件。

饱和弧:流量等于容量。

非饱和弧:流量小于容量。

零流弧:流量等于零。

非零流弧:流量大于零。

链:顶点序列(u,a,b,c....,v)为一条链,链中的弧的方向不一定要求一致。因此一条链中有前向弧(其集合记作P+)和向弧(其集合记作P-)。

前向弧:方向从u指向v的弧。

后向弧:方向从v指向u的弧。

前向弧和后向弧都是相对的,根据指定链的方向而决定。

增广路假设P是一条从源点到汇点的链。P中所有的前向弧满足0<=f(u,v)<c(u,v),即P+不饱和;P中所有的后向弧满足0<f(u,v)<=c(u,v),即P-都是非零流弧。那么P就是一条增广路(增广路不一定是一条路径)。

层次网络:给每个点(用BFS)赋好它的所在层数,针对一个点我们只需要每次看它所指向的在下一层的点,这样的弧叫做允许弧


下面用我的话说说“最大流Dinic算法”的思想

其实就是求从源点最多能流出多少流量,能够到达汇点

想象现在给你的图就是一张“水管图”,每条水管上都有一个数代表该条水管的容量,你现在就是要依次去找每一条增广路,看看这条路上的水管的最小容量是多少,那么就表示往这条路上最多可以通多少水(流量)!那么那些没有被灌满的管道(即这次通的流量<容量),还可以被灌水,所以标记的容量应该改为“原容量-当前流量”。然后就接着找下一条增广路了!直到所有增广路都找到了,就说明这些水管的流量之和即这张图的“最大流”!



需要get的点

1.在建弧的时候要同时连一个反向弧,这样是让你能够反悔:


2. bfs更新层次是在每找完一条增广路之后都要更新,在代码中(可见下方代码部分):

 if (e[i].c > 0 && d[v] == -1)

if条件:①这个边的容量仍>0。否则说明这条水管已经被灌满了或者说不能被灌。②这个点的层次还没赋(相当于bfs的visit数组)

    return (d[T] != -1);

如果return false说明终点的层次赋不了,意思就是没有增广路了。

3. dfs的flow和res参数

flow表示之前的边里面的最小容量(最大流量)。flow减去你之后所能分走的最大流量(由递归求)来看还可不可以从这个节点再分流量出去。

res表示当前节点及之后节点中的最小容量(最大流量),返回给上一个节点用(想递归过程)。

炸点优化:如果res==0表示我当前点出去是流不到终点的,那么就不要走我了,把我的层次值赋为-1。(能保证在这条增广路的寻找中是不会再走你了)

4. 牛逼的e[i^1]

我从0开始存边(不能从1开始存边哟!),每次存一个正向边都会紧接着存它的反向边,这样的话 i^1就表示正(反)向边i的那个反(正)向边。因为与1异或,就是奇变偶,偶变奇的运算。


【模板代码】(其实dinic算法就是模板,网络流的题 更重要的是如何“建图”)

const int MAX_N = 100;  // X 集合中的顶点数上限
const int MAX_M = 10000;  // 总的边数上限
struct edge {
    int v, c, next;  // v 是指边的另一个顶点,c 表示容量
} e[MAX_M];
int p[MAX_N], eid;
void init() {
    memset(p, -1, sizeof(p));
    eid = 0;
}
void insert(int u, int v, int c) {  // 插入一条从 u 连向 v,容量为 c 的弧
    e[eid].v = v;
    e[eid].c = c;
    e[eid].next = p[u];
    p[u] = eid++;
}
void addedge(int u, int v, int c) {  // 用 insert2 来插入网络中的弧
    insert(u, v, c);
    insert(v, u, 0);  // 插入一条方向相反、当前容量为 0 的弧
}
int S, T;  // S 是源点,T 是汇点
int d[MAX_N];  // 存储每个顶点的层次
bool bfs() {
    memset(d, -1, sizeof(d));
    queue<int> q;
    q.push(S);
    d[S] = 0;
    while (q.empty()==false) {
        int u = q.front();
        q.pop();
        for (int i = p[u]; i != -1; i = e[i].next) {
            int v = e[i].v;
            if (e[i].c > 0 && d[v] == -1) {
                q.push(v);
                d[v] = d[u] + 1;
            }
        }
    }
    return (d[T] != -1);
}

int dfs(int u, int flow) {  // flow 表示当前搜索分支的流量上限
    if (u == T) {
        return flow;
    }
    int res = 0;
    for (int i = p[u]; i != -1; i = e[i].next) {
        int v = e[i].v;
        if (e[i].c > 0 && d[u] + 1 == d[v]) {
            int tmp = dfs(v, min(flow, e[i].c));  // 递归计算顶点 v,用 c(u, v) 来更新当前流量上限
            flow -= tmp;
            e[i].c -= tmp;
            res += tmp;
            e[i ^ 1].c += tmp;  // 修改反向弧的容量
            if (flow == 0) {  // 流量达到上限,不必继续搜索了
                break;
            }
        }
    }
    if (res == 0) {  // 当前没有经过顶点 u 的可行流,不再搜索顶点 u
        d[u] = -1;
    }
    return res;
}

int maxflow() {  // 函数返回值就是最大流的结果
    int res = 0;
    while (bfs()) {
        res += dfs(S, INF);  // 初始流量上限为 INF 
    }
    return res;
}

巩固理解:

dfs(int s,int flow)的意思是在这条增广路上,到达s点之前的边所更新出的最大流量(最小容量)是flow,而dfs的返回值res是s点及s点之后的边所更新出的最大流量(最小容量)。那么dfs(0,0x3f3f3f3f)就是指0点及0点之后找到的一条增广路的最大流量。



I have so much to learn........Come on!!!

猜你喜欢

转载自blog.csdn.net/m0_38033475/article/details/80143084