网络流

网络流入门到入坟

\(1.\) 流网络

\(G=(V,E)\):有向图,s:源点,t:汇点,边权:流量。不存在反向边(存在可以加点)。

可行流 \(f\):容量限制,流量守恒对于每个点流入流出相同(不考虑反向边)。

最大流:最大可行流。

残留网络:与可行流一一对应,由原图边和反向边组成,原图边,容量减流量,原图反向边,退流。

原网络的可行流加残留网络的可行流也是原网络的可行流。流量值相加。

增广路径:在残留网络里,沿着边权大于零的边走,如果能到达重点则称为增广路径。

若可行流没有增广路径,则该可行流为最大流。

割:把点集分成两个子集:\(S\)\(T\)\(s∈S\)\(t∈T\)

割的容量:由 \(S\) 指向 \(T\) 的边权之和。(不考虑反向边)

割的流量:由 \(S\) 指向 \(T\) 的流量之和减去由 \(T\) 指向 \(S\) 的流量之和。

割的流量一定小于等于割的容量。

最大流小于等于最小割

\(2.\) 最大流最小割定理

最大流 \(f\) 的残留网络中不存在增广路。存在割的流量等于最大流的流量。

\(EK\) 算法:复杂度 \(O(nm^2)\)。迭代,每次先找一条增广路,更新残留网络,求增广路径上容量的最小值,更新所有正向边减去 \(k\),所有反向边增加 \(k\),利用一个 \(pre\) 数组记录一下路径。

const int N = 1e3 + 10;
const int M = 2e4 + 10; //残留网络
const int inf = 1e8;

int n, m, S, T;
int h[N], e[M], f[M], ne[M], idx; //f容量
int q[N], d[N], pre[N];//宽搜队列,记录从起点到当前点的容量最小值
bool st[N];

inline void add(int a, int b, int c){//维护残留网络
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++;//容量
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++;//反向边容量为0 
}

inline bool bfs(){//bfs找增广路
    int hh = 0, tt = 0;
    memset(st, false, sizeof st);
    q[0] = S, st[S] = true, d[S] = inf;
    while(hh <= tt){
        int t = q[hh ++];//取队头元素
        for(int i = h[t]; ~ i; i = ne[i]){//遍历出边
            int ver = e[i];
            if(!st[ver] && f[i]){//没有经历过且容量大于零
                st[ver] = true;//遍历过
                d[ver] = min(d[t], f[i]);//维护最小容量
                pre[ver] = i;//更新pre数组
                if(ver == T) return true;//找到了
                q[++ tt] = ver;//入队列
            }
        }
    }
    return false;
}

inline int EK(){
    int r = 0;
    while(bfs()){
        r += d[T];
        for(int i = T; i != S; i = e[pre[i] ^ 1]) //从后向前找反向边
            f[pre[i]] -= d[T], f[pre[i] ^ 1] += d[T];//将使用的流量更新残留网络
    }
    return r;//返回最大流
}

signed main(){
    read(n), read(m), read(S), read(T);
    memset(h, -1, sizeof h);
    while(m --){
        int a, b, c;
        read(a), read(b), read(c);
        add(a, b, c);
    }
    print(EK());
    return 0;
}

\(Dinic\) 算法:复杂度 \(O(n^2m)\)

const int N = 1e4 + 10;
const int M = 2e5 + 10;
const int inf = 1e8;

int n, m, S, T;
int h[N], e[M], f[M], ne[M], idx; //f容量
int q[N], d[N], cur[N];//优化

inline void add(int a, int b, int c){//维护残留网络
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++;//容量
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++;//反向边容量为0 
}
//当前弧优化
//记录流满的边数,从下一条边开始枚举

inline bool bfs(){  //判断有无增广路,并建立分层图
    int hh = 0, tt = 0;
    memset(d, -1, sizeof d);
    q[0] = S, d[S] = 0, cur[S] = h[S];
    while (hh <= tt){
        int t = q[hh ++ ];
        for (int i = h[t]; ~i; i = ne[i]){
            int ver = e[i];
            if (d[ver] == -1 && f[i]){ //没有被搜过,且容量大于0
                d[ver] = d[t] + 1;//层数,其实就是到起点的最短距离
                cur[ver] = h[ver];
                if (ver == T)  return true;
                q[ ++ tt] = ver; //没找到加入队列继续宽搜
            }
        }
    }
    return false;
}

inline int find(int u, int limit){//从起点开始能流到u的最大流为limit
    if (u == T) return limit;
    int flow = 0; //把当前满的跳过
    for (int i = cur[u]; ~i && flow < limit; i = ne[i]){
        cur[u] = i;  // 当前弧优化
        int ver = e[i];
        if (d[ver] == d[u] + 1 && f[i]){//搜下一层
            int t = find(ver, min(f[i], limit - flow));//流量
            if (!t) d[ver] = -1;//删点
            f[i] -= t, f[i ^ 1] += t, flow += t;
        }
    }
    return flow;
}

inline int dinic(){
    int r = 0, flow;
    while (bfs()) while (flow = find(S, inf)) r += flow;
    //只要有增广路,在当前残留网络分层图中寻找所有增广路径累加增广流量
    return r;
}

signed main(){
    scanf("%d%d%d%d", &n, &m, &S, &T);
    memset(h, -1, sizeof h);
    while (m -- ){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }
    printf("%d\n", dinic());
    return 0;
}

\(3.\) 题目

用网络流解题,需要可行流与可行解一一对应。

\(4.\) 费用流

在一个流网络里,所有最大可行流中费用的最小、最大值。

最小费用最大流:退费用,\(SPFA\)求一条源点到汇点的最短路,更新残留网络,(\(EK\))。

const int N = 5e3 + 10;
const int M = 2e5 + 10;
const int inf = 1e8;

int n, m, S, T;
int h[N], e[M], f[M], w[M], ne[M], idx;
int q[N], d[N], pre[N], incf[N];
//记录推最短路,走到每个点的最大流量
bool st[N];

inline void add(int a, int b, int c, int d){
    e[idx] = b, f[idx] = c, w[idx] = d, ne[idx] = h[a], h[a] = idx ++;
    e[idx] = a, f[idx] = 0, w[idx] = -d, ne[idx] = h[b], h[b] = idx ++;
}

inline bool spfa(){
    int hh = 0, tt = 1;
    memset(d, 0x3f, sizeof d);
    memset(incf, 0, sizeof incf);
    q[0] = S, d[S] = 0, incf[S] = inf;
    while(hh != tt){
        int t = q[hh ++];
        if(hh == N) hh = 0;
        st[t] = false;
        for(int i = h[t]; ~ i; i = ne[i]){
            int ver = e[i];
            if(f[i] && d[ver] > d[t] + w[i]){
                d[ver] = d[t] + w[i];
                pre[ver] = i;
                incf[ver] = min(f[i], incf[t]);
                if(!st[ver]){
                    q[tt ++] = ver;
                    if(tt == N) tt = 0;
                    st[ver] = true;
                }
            }
        }
    }
    return incf[T] > 0;
}

inline void EK(int &flow, int &cost){
    flow = cost = 0;
    while(spfa()){
        int t = incf[T];
        flow += t, cost += t * d[T];
        for(int i = T; i != S; i = e[pre[i] ^ 1]){
            f[pre[i]] -= t, f[pre[i] ^ 1] += t;
        }
    }
}

signed main(){
    read(n), read(m), read(S), read(T);
    memset(h, -1, sizeof h);
    while(m --){
        int a, b, c, d;
        read(a), read(b), read(c), read(d);
        add(a, b, c, d);
    }
    int flow, cost;
    EK(flow, cost);
    print(flow), cout << " ", print(cost);
    return 0;
}
posted @ 2022-06-23 22:22  Altwilio  阅读(72)  评论(0编辑  收藏  举报