网络流笔记

Dinic 算法求最大流#

算法思想

首先用bfs通过网络流(残量)将图分层, 然后dfs搜索分层图,一次dfs可以搜出多条可行流, 但是这些可行流所经过的边数目都是相等的。

时间复杂度为θ(n2m)

但是实际上在问题中,及少可以卡的满。 而且很多网络流问题是二分图, 这时时间复杂度约为θ(nm)

代码实现
code
namespace flow{
    int dep[N + 5], cur[N + 5], h[N + 5], S, T; 
    struct Edge{
        int v, nxt, val;
    }e[M + 5];
    int tot;

    void add(int x, int y, int w) {
        e[++tot] = Edge{y, h[x], w};
        h[x] = tot;
        e[++tot] = Edge{x, h[y], 0};
        h[y] = tot;
    }
    void init() {

        flow::S = n + 1; flow::T = n + 2; 
        tot = 1;
        memset(h, 0, sizeof(h));
    }
    queue<int > q;
    int bfs() {
        memset(dep, 0, sizeof(dep)); q.push(S); dep[S] = 1;
        while(!q.empty()) {
            int u = q.front(); q.pop(); cur[u] = h[u];
            for(int i = h[u], v; i; i = e[i].nxt)
                if (e[i].val && !dep[v = e[i].v]) {
                    dep[v] = dep[u] + 1;
                    q.push(v);
                }
        }
        return dep[T];
    }

    int dfs(int u, int flow) {
        if (u == T || !flow) 
            return flow;

        int ret = 0;
        for(int &i = cur[u]; i; i = e[i].nxt) {
            int v = e[i].v, d;
            if ((dep[v] == dep[u] + 1) && (d = dfs(v, min(flow - ret, e[i].val)))) {
                e[i].val -= d; e[i ^ 1].val += d; ret += d;
                if (ret == flow) return flow;
            }
        }

        return ret;
    }
    int dinic() {
        int ans = 0;
        while(bfs()) 
            ans += dfs(S, inf);
        
        return ans;
    }
};

MCMF 费用流(spfa)#

算法思想

因为要求在流量最大的情况下要求费用最小, 所以考虑按照费用从小到大增广每一条可行流, 显然一条可行流的费用是路径上所有边的权值和。 所以我们可以采用SPFA算法。在SPFA的过程中记下路径, 然后依次对路径所有边的残量进行操作。 其实和EK算法很像,一次增广一条路径。

代码实现
code
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <bits/stdc++.h>
const int N = 1e5;
using namespace std;

int n, m, s, t, u, v, w, c;
void read(int &x) {
  x = 0; int w = 1; char c = getchar(); for(; c < '0' || c > '9'; c = getchar()) if(c == '-') w = -1;
  for(; c <= '9' && c >= '0'; c = getchar()) x = x * 10 + c - '0'; x *= w;
}

struct Edge{
  int v, nxt, val, dis;
}e[N + 5];
int h[N + 5], tot, inq[N + 5], dis[N + 5], flow[N + 5], pre[N + 5];
int mf, mc, bk[N + 5];
void add(int x, int y, int z, int w) {
  e[++tot] = Edge{y, h[x], z, w};
  h[x] = tot;
}

queue<int > q;

int spfa() {
  memset(inq, 0, sizeof(inq)); memset(dis, 0x7f, sizeof(dis)); memset(flow, 0x7f, sizeof(flow));
  dis[s] = 0; inq[s] = 1; pre[t] = -1; q.push(s);
  while(!q.empty()) {
    int u = q.front(); q.pop(); inq[u] = 0;
    // cout << u << endl; 
    for(int i = h[u], v; i; i = e[i].nxt)
      if (e[i].val && dis[u] + e[i].dis < dis[v = e[i].v]) {
        dis[v] = dis[u] + e[i].dis;
        flow[v] = min(flow[u], e[i].val);
        pre[v] = u; bk[v] = i;
        if (!inq[v]) {
          q.push(v);
          // cout << u << ' ' << v << endl;
          inq[v] = 1;
        }
      }
  }
  // cout << pre[t] << endl;
  return pre[t] != -1;
}

int main() {
  // freopen("t.in", "r", stdin);
  read(n); read(m); read(s); read(t); tot = 1;
  for(int i = 1; i <= m; ++i) {
    read(u); read(v); read(w); read(c);
    add(u, v, w, c); add(v, u, 0, -c);
  }
  while(spfa()) {
    // cout << flow[t] << endl;
    mf += flow[t]; mc += flow[t] * dis[t];
    int now = t;
    while(now != s) {
      e[bk[now]].val -= flow[t]; e[bk[now]^1].val += flow[t];
      now = pre[now];
    }
  }
  printf("%d %d\n", mf, mc);
  return 0;
}

有上下界的最大流#

算法思想

一个自然的想法就用 上界-下界 作为新网络的容量, 但是这样实际(加上默认的下界流量)不符合流量平衡。 然而在网络流中源点和汇点是可以不符合流量平衡的。 考虑在网络外再建一个源点和一个汇点, 定义 ini表示所有v=i的边u,v的下界和, outi表示所有u=i的边(u,v)的下界和。 然后对于所有ini>outi的点,从新建的Si引一条容量iniouti的边, 对于ini<outi的边, 向新建的汇点T建一条outiini的边。 同时为了流量平衡从原来t向原来s连一条容量为的边。

然后我们可以跑一遍最大流记为k, 如果k此时不等于从S出的容量和, 显然无法满足下界,输出无解。
如果等于, 记加入的t>s的反向边的容量为flow1, 所以我们可以去掉ST以及(t,s)的边, 然后在原来残量网络上再跑一次最大流记为flow2
那么答案等于flow1+flow2

注意这里flow1不等于从S出发的容量(这样写,在洛谷能过,但FZOJ和LOJ过不了), 因为从S出发的容量只是使其容量平衡的, 只有经过t然后流会s绕一圈才是从st的真实流量。

代码实现
code
#include <stdio.h>
#include <queue>
#include <iostream>
#include <algorithm>
#include <bits/stdc++.h>
#define LL long long
const int N = 400, M = 2e5;
#define inf 100000000
using namespace std;

#define L(i, s, t) for(int i = s; i <= t; ++i)
#define R(i, t, s) for(int i = t; i >= s; --i)

int n, m, s, t, S, T, h[N + 5], d[N + 5];
int dep[N + 5], cur[N + 5]; 

struct Edge{
	int v, nxt, val;
}e[M + 5];
int tot;

void add(int x, int y, int w) {
	e[++tot] = Edge{y, h[x], w};
	h[x] = tot;
	e[++tot] = Edge{x, h[y], 0};
	h[y] = tot;
}

queue<int > q;
int bfs() {
	memset(dep, 0, sizeof(dep)); q.push(S); dep[S] = 1;
	while(!q.empty()) {
		int u = q.front(); q.pop(); cur[u] = h[u];
		for(int i = h[u], v; i; i = e[i].nxt)
			if (e[i].val && !dep[v = e[i].v]) {
				dep[v] = dep[u] + 1;
				q.push(v);
			}
	}
	return dep[T];
}

int dfs(int u, int flow) {
	if (u == T || !flow) 
		return flow;

	int ret = 0;
	for(int &i = cur[u]; i; i = e[i].nxt) {
		int v = e[i].v, d;
		if ((dep[v] == dep[u] + 1) && (d = dfs(v, min(flow - ret, e[i].val)))) {
			e[i].val -= d; e[i ^ 1].val += d; ret += d;
			if (ret == flow) return flow;
		}
	}

	return ret;
}

int dinic() {
	int ans = 0;
	// cout << "what" << endl;
	while(bfs()) 
		ans += dfs(S, inf);
	
	return ans;
}

int main() {
	// freopen("t.in", "r", stdin); 
	tot = 1;
	scanf("%d%d%d%d", &n, &m, &s, &t); S = n + 1; T = n + 2; int sta = 0;
	L(i, 1, m) {
		int u, v, low, upp;
		scanf("%d%d%d%d", &u, &v, &low, &upp);
		add(u, v, upp - low);
		d[v] += low; d[u] -= low;
	}
	L(i, 1, T)
		if (d[i] > 0) add(S, i, d[i]), sta += d[i];
		else add(i, T, -d[i]);
	add(t, s, inf);
	int ans = dinic();
	// cout << e[tot - 1].val << endl;
	if (ans != sta)printf("Impossble\n");
	else {
		ans = e[tot].val;
		e[tot].val = 0; e[tot - 1].val = 0;
		S = s; T = t; ans += dinic();
		printf("%d\n", ans);
	}
	return 0;
}

有上下界的最小流#

算法思想

基本等于上面的的 有上下界的最大流, 唯一不同的是 找出可行流后要想办法减流, 删去(t,s)边, 然后ans减去从ts的最大流,相当于退流。

代码实现
code
#include <stdio.h>
#include <queue>
#include <iostream>
#include <algorithm>
#include <bits/stdc++.h>
#define LL long long
const int N = 6e4, M = 5e5;
#define inf 2147483647
using namespace std;

#define L(i, s, t) for(int i = s; i <= t; ++i)
#define R(i, t, s) for(int i = t; i >= s; --i)

int n, m, s, t, S, T, h[N + 5], d[N + 5];
int dep[N + 5], cur[N + 5]; 

struct Edge{
	int v, nxt, val;
}e[M + 5];
int tot;

void add(int x, int y, int w) {
	e[++tot] = Edge{y, h[x], w};
	h[x] = tot;
	e[++tot] = Edge{x, h[y], 0};
	h[y] = tot;
}

queue<int > q;
int bfs() {
	memset(dep, 0, sizeof(dep)); q.push(S); dep[S] = 1;
	while(!q.empty()) {
		int u = q.front(); q.pop(); cur[u] = h[u];
		for(int i = h[u], v; i; i = e[i].nxt)
			if (e[i].val && !dep[v = e[i].v]) {
				dep[v] = dep[u] + 1;
				q.push(v);
			}
	}
	return dep[T];
}

int dfs(int u, int flow) {
	if (u == T || !flow) 
		return flow;

	int ret = 0;
	for(int &i = cur[u]; i; i = e[i].nxt) {
		int v = e[i].v, d;
		if ((dep[v] == dep[u] + 1) && (d = dfs(v, min(flow - ret, e[i].val)))) {
			e[i].val -= d; e[i ^ 1].val += d; ret += d;
			if (ret == flow) return flow;
		}
	}

	return ret;
}

int dinic() {
	int ans = 0;
	// cout << "what" << endl;
	while(bfs()) 
		ans += dfs(S, inf);
	
	return ans;
}

int main() {
	// freopen("t.in", "r", stdin); 
	tot = 1;
	scanf("%d%d%d%d", &n, &m, &s, &t); S = n + 1; T = n + 2; int sta = 0;
	L(i, 1, m) {
		int u, v, low, upp;
		scanf("%d%d%d%d", &u, &v, &low, &upp);
		add(u, v, upp - low);
		d[v] += low; d[u] -= low;
	}
	L(i, 1, T)
		if (d[i] > 0) add(S, i, d[i]), sta += d[i];
		else add(i, T, -d[i]);
	add(t, s, inf);
	int ans = dinic();
	// cout << e[tot - 1].val << endl;
	if (ans != sta)printf("please go home to sleep\n");
	else {
		ans = e[tot].val;
		e[tot].val = 0; e[tot - 1].val = 0;
		S = t; T = s; ans -= dinic();
		printf("%d\n", ans);
	}
	return 0;
}

待补

dijkstra费用流#

有上下界费用流#

带负圈的费用流#

作者:Aurora

出处:https://www.cnblogs.com/aurora2023/p/17092109.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Absolutey  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示