网络流最大流EK算法

发布时间 2023-12-05 15:26:09作者: NOTHINGBUTNOTHING
```cpp
/*
总的思路就是找还有哪些路可以走,只要找到新的路,流量就增加了
需要注意的是,这里面反向边的含义,可以大致理解为,找路的过程是随机的,可能找到的不是最优的,
那么,加一条反向边,后面就有可能找到这个反向边来走,这样就相当于弥补了以前的错误,相当于走了正确的道路

*/

#include <iostream>
using namespace std;
#include <cstring>
#include <algorithm>
#include <cstring>
#include <vector>
#include <iomanip>
#include <stdlib.h>
#include <unordered_map>
#include <queue>
typedef long long ll;
const int N = 100020, M = 2 * N;
struct Edge
{
    ll v, c, ne;
} e[M];      // 存边的结构体,v表示边的终点,c表示剩下的容量,ne表示下一条边在边集中的位置
int idx = 1; /*这里之所以从1开始,是因为这里面要处理反向边,
             我们为了方便找到一条边的反向边,
             让正向边和对应的反向边在边集数组中相邻,两两配对
             也就是add(a,b,c);紧接着add(b,a,0);反向边初始化为0
             而我们在这里用i!=0来遍历边,所以0和1这一对就不能用了,
             我们存边用e[++idx]={},这样从2和3开始配对
             ps:相邻两个数(要求是偶数在前,奇数在后)满足:a^1=b,b^1=a,因为异或是不进位加法
             */
int h[N];    // 链表头节点指针数组,存储的是每个点的第一个出边在边集数组中的位置

int n, m;
int S, T; // 源点和汇点

ll mf[N];  // 从源点到v的路径上的流量上限
ll pre[N]; // 每个节点的前驱边在边集中的位置

void add(int a, int b, int c)
{
    e[++idx] = {b, c, h[a]};
    h[a] = idx;
}

bool bfs() // bfs找从源点到汇点的路径,如果能找到就返回true,否则返回false
{
    memset(mf, 0, sizeof mf); // 初始化当前查找的路径的上,从源点到各个节点的流上限
    // 同时mf还可以用来判断有没有被遍历
    queue<int> q;
    q.push(S); // 源点入队

    mf[S] = 0x3f3f3f3f; // S到S的流量上限设为“无穷大”,不影响后面求流量上限

    while (q.size())
    {
        auto u = q.front();
        q.pop();

        for (int i = h[u]; i; i = e[i].ne) // 遍历这个点的出边
        {
            ll v = e[i].v; // 取出这条边的重点

            if (mf[v] == 0 && e[i].c) // 如果这个边的终点还没有被遍历,也就是这个边没有被遍历
                                      //! 并且,这条边的容量还没有被用完,那就可以走这条边
            {
                mf[v] = min(mf[u], e[i].c); // 那么到v这个边的流量上限就是到u的流量上限和这条边的剩余容量的最小值

                pre[v] = i; // 存储前驱边

                q.push(v); // v入队

                if (v == T)
                {
                    // 如果找到了汇点,那就找到了一个可行的路径,直接返回true
                    return true;
                }
            }
        }
    }

    return false;
}

ll EK() // 用于把可行的流量累加起来
{
    ll flow = 0;

    while (bfs()) // 每次先找一条可行的路径
    {
        // 如果还找的到
        int v = T; // 从汇点反向找到源点,更新本次bfs找到的这条路径上各个边的剩余容量
        while (v != S)
        {
            int i = pre[v]; // 找到这个点的前驱边

            e[i].c -= mf[T]; // 从源点到汇点的流量上限就是这条路的流量

            e[i ^ 1].c += mf[T]; // 给这个前驱边的反向边增加反向的流量,让后面的尝试可以选择把这条路退回来
            v = e[i ^ 1].v;      // 反向边的终点就是v的前驱点
        }
        flow += mf[T];
    }

    return flow;
}

int main()
{
    cin >> n >> m >> S >> T;

    for (int i = 1; i <= m; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;

        add(a, b, c);
        add(b, a, 0);
    }

    cout << EK();

    return 0;


}