想看定义和证明可以到算法学习笔记(28): 网络流 - 知乎
最大流
EK算法
时间复杂度O() 较慢
1.残留网络的可行流+原图的可行流=原题的另一个可行流
2.可行流的残留网络中不存在增广路,那么就找到了最大流
例题 求一个网络的最大流
EK算法时间复杂度为O(点数*边数^2),但是这个复杂度非常玄学,上界非常宽松
基本可以解决点数+边数<10000的问题
from collections import defaultdict, deque
class EK:
def __init__(self, n, m, s, t):
self.flow = [0] * (n + 10)
self.pre = [0] * (n + 10)
self.used = set()
self.g = defaultdict(list)
self.edges_val = defaultdict(int)
self.m = m
self.s = s
self.t = t
self.res = 0
def addEdge(self, From, to, flow):
self.edges_val[(From, to)] += flow
self.edges_val[(to, From)] += 0
self.g[From].append(to)
self.g[to].append(From)
def bfs(self) -> bool:
self.used.clear()
q = deque()
q.append(self.s)
self.used.add(self.s)
self.flow[self.s] = float('inf')
while q:
now = q.popleft()
for nxt in self.g[now]:
edge = (now, nxt)
val = self.edges_val[edge]
if nxt not in self.used and val:
self.used.add(nxt)
self.flow[nxt] = min(self.flow[now], val)
self.pre[nxt] = now
if nxt == self.t:
return True
q.append(nxt)
return False
def EK(self) -> int:
while self.bfs():
self.res += self.flow[self.t]
From = self.t
to = self.pre[From]
while True:
edge = (From, to)
reverse_edge = (to, From)
self.edges_val[edge] += self.flow[self.t]
self.edges_val[reverse_edge] -= self.flow[self.t]
if to == self.s:
break
From = to
to = self.pre[From]
return self.res
n, m, s, t = map(int, input().split())
ek = EK(n, m, s, t)
for _ in range(m):
a, b, c = map(int, input().split())
ek.addEdge(a, b, c)
print(ek.EK())
Dinic算法
时间复杂度O() 较快,此算法为求最大流最常用算法
// from : https://oi-wiki.org/graph/flow/max-flow/#dinic
#define maxn 250
#define INF 0x3f3f3f3f
struct Edge {
int from, to, cap, flow;
Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}
};
struct Dinic {
int n, m, s, t;
vector<Edge> edges;
vector<int> G[maxn];
int d[maxn], cur[maxn];
bool vis[maxn];
void init(int n) {
for (int i = 0; i < n; i++) G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int cap) {
edges.push_back(Edge(from, to, cap, 0));
edges.push_back(Edge(to, from, 0, 0));
m = edges.size();
G[from].push_back(m - 2);
G[to].push_back(m - 1);
}
bool BFS() {
memset(vis, 0, sizeof(vis));
queue<int> Q;
Q.push(s);
d[s] = 0;
vis[s] = 1;
while (!Q.empty()) {
int x = Q.front();
Q.pop();
for (int i = 0; i < G[x].size(); i++) {
Edge& e = edges[G[x][i]];
if (!vis[e.to] && e.cap > e.flow) {
vis[e.to] = 1;
d[e.to] = d[x] + 1;
Q.push(e.to);
}
}
}
return vis[t];
}
int DFS(int x, int a) {
if (x == t || a == 0) return a;
int flow = 0, f;
for (int& i = cur[x]; i < G[x].size(); i++) {
Edge& e = edges[G[x][i]];
if (d[x] + 1 == d[e.to] && (f = DFS(e.to, min(a, e.cap - e.flow))) > 0) {
e.flow += f;
edges[G[x][i] ^ 1].flow -= f;
flow += f;
a -= f;
if (a == 0) break;
}
}
return flow;
}
int Maxflow(int s, int t) {
this->s = s;
this->t = t;
int flow = 0;
while (BFS()) {
memset(cur, 0, sizeof(cur));
flow += DFS(s, INF);
}
return flow;
}
};
Dinic算法 - 解决男女配对问题
- 创建一个超级源点,一个超级汇点
- 源点流向所有男生,所有女生流向汇点,容量为1
- 男生流向自己喜欢的女生,容量为1
- 这个网络的最大流就是最大配对
// https://blog.csdn.net/nreyong/article/details/122931638?spm=1001.2014.3001.5501
class Solution {
public:
#define maxn 505
#define INF 0x3f3f3f3f
struct Edge {
int from, to, cap, flow;
Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}
};
struct Dinic {
int n, m, s, t;
vector<Edge> edges;
vector<int> G[maxn];
int d[maxn], cur[maxn];
bool vis[maxn];
void init(int n) {
for (int i = 0; i < n; i++) G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int cap) {
edges.push_back(Edge(from, to, cap, 0));
edges.push_back(Edge(to, from, 0, 0));
m = edges.size();
G[from].push_back(m - 2);
G[to].push_back(m - 1);
}
bool BFS() {
memset(vis, 0, sizeof(vis));
queue<int> Q;
Q.push(s);
d[s] = 0;
vis[s] = 1;
while (!Q.empty()) {
int x = Q.front();
Q.pop();
for (int i = 0; i < G[x].size(); i++) {
Edge& e = edges[G[x][i]];
if (!vis[e.to] && e.cap > e.flow) {
vis[e.to] = 1;
d[e.to] = d[x] + 1;
Q.push(e.to);
}
}
}
return vis[t];
}
int DFS(int x, int a) {
if (x == t || a == 0) return a;
int flow = 0, f;
for (int& i = cur[x]; i < G[x].size(); i++) {
Edge& e = edges[G[x][i]];
if (d[x] + 1 == d[e.to] && (f = DFS(e.to, min(a, e.cap - e.flow))) > 0) {
e.flow += f;
edges[G[x][i] ^ 1].flow -= f;
flow += f;
a -= f;
if (a == 0) break;
}
}
return flow;
}
int Maxflow(int s, int t) {
this->s = s;
this->t = t;
int flow = 0;
while (BFS()) {
memset(cur, 0, sizeof(cur));
flow += DFS(s, INF);
}
return flow;
}
};
int maximumInvitations(vector<vector<int>>& grid) {
Dinic D;
int n = grid.size(), m = grid[0].size();
D.n = n + m + 10;
D.init(D.n);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j]) {
D.AddEdge(i, n + j, 1);
}
}
}
for (int i = 0; i < grid.size(); i++) {
D.AddEdge(500, i, 1);
}
for (int i = 0; i < grid[0].size(); i++) {
D.AddEdge(n + i, 501, 1);
}
D.s = 500, D.t = 501;
return D.Maxflow(D.s, D.t);
}
};
最小费用最大流
例题:【模板】最小费用最大流
// from : https://oi-wiki.org/graph/flow/min-cost/
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>
#define N 5005
#define M 100005
const int INF = 0x3f3f3f3f;
int n, m, tot = 1, lnk[N], cur[N], ter[M], nxt[M], cap[M], cost[M], dis[N], min_cost;
bool vis[N];
void add(int u, int v, int w, int c) {
ter[++tot] = v, nxt[tot] = lnk[u], lnk[u] = tot, cap[tot] = w, cost[tot] = c;
}
void addedge(int u, int v, int w, int c) { add(u, v, w, c), add(v, u, 0, -c); }
bool spfa(int s, int t) {
memset(dis, 0x3f, sizeof(dis));
memcpy(cur, lnk, sizeof(lnk));
std::queue<int> q;
q.push(s), dis[s] = 0, vis[s] = 1;
while (!q.empty()) {
int u = q.front();
q.pop(), vis[u] = 0;
for (int i = lnk[u]; i; i = nxt[i]) {
int v = ter[i];
if (cap[i] && dis[v] > dis[u] + cost[i]) {
dis[v] = dis[u] + cost[i];
if (!vis[v]) q.push(v), vis[v] = 1;
}
}
}
return dis[t] != INF;
}
int dfs(int u, int t, int flow) {
if (u == t) return flow;
vis[u] = 1;
int ans = 0;
for (int &i = cur[u]; i && ans < flow; i = nxt[i]) {
int v = ter[i];
if (!vis[v] && cap[i] && dis[v] == dis[u] + cost[i]) {
int x = dfs(v, t, std::min(cap[i], flow - ans));
if (x) min_cost += x * cost[i], cap[i] -= x, cap[i ^ 1] += x, ans += x;
}
}
vis[u] = 0;
return ans;
}
int mcmf(int s, int t) {
int ans = 0;
while (spfa(s, t)) {
int x;
while ((x = dfs(s, t, INF))) ans += x;
}
return ans;
}
int main() {
int s, t;
scanf("%d%d%d%d", &n, &m, &s, &t);
while (m--) {
int u, v, w, c;
scanf("%d%d%d%d", &u, &v, &w, &c);
addedge(u, v, w, c);
}
int max_flow = mcmf(s, t);
printf("%d %d\n", max_flow, min_cost);
return 0;
}
例题:两个数组最小的异或值之和
建图:
- 从源点连容量为1,费用为0的边到nums1的每个数字
- 从nums2的每个数字连容量为1,费用为0的边到汇点
- 从每个数字nums1[i]向每个nums2[j]连容量为1,费用为nums1[i]^nums2[j]的边
- 此时最大流就是nums1中数字个数,最小费用就是两个数组最小的异或值之和
class Solution {
public:
#define N 5005
#define M 100005
const int INF = 0x3f3f3f3f;
int n, m, tot = 1, lnk[N], cur[N], ter[M], nxt[M], cap[M], cost[M], dis[N], min_cost;
bool vis[N];
void add(int u, int v, int w, int c) {
ter[++tot] = v, nxt[tot] = lnk[u], lnk[u] = tot, cap[tot] = w, cost[tot] = c;
}
void addedge(int u, int v, int w, int c) { add(u, v, w, c), add(v, u, 0, -c); }
bool spfa(int s, int t) {
memset(dis, 0x3f, sizeof(dis));
memcpy(cur, lnk, sizeof(lnk));
std::queue<int> q;
q.push(s), dis[s] = 0, vis[s] = 1;
while (!q.empty()) {
int u = q.front();
q.pop(), vis[u] = 0;
for (int i = lnk[u]; i; i = nxt[i]) {
int v = ter[i];
if (cap[i] && dis[v] > dis[u] + cost[i]) {
dis[v] = dis[u] + cost[i];
if (!vis[v]) q.push(v), vis[v] = 1;
}
}
}
return dis[t] != INF;
}
int dfs(int u, int t, int flow) {
if (u == t) return flow;
vis[u] = 1;
int ans = 0;
for (int &i = cur[u]; i && ans < flow; i = nxt[i]) {
int v = ter[i];
if (!vis[v] && cap[i] && dis[v] == dis[u] + cost[i]) {
int x = dfs(v, t, std::min(cap[i], flow - ans));
if (x) min_cost += x * cost[i], cap[i] -= x, cap[i ^ 1] += x, ans += x;
}
}
vis[u] = 0;
return ans;
}
int mcmf(int s, int t) {
int ans = 0;
while (spfa(s, t)) {
int x;
while ((x = dfs(s, t, INF))) ans += x;
}
return ans;
}
int minimumXORSum(vector<int>& nums1, vector<int>& nums2) {
for (int i = 0; i < nums1.size(); i++) {
for (int j = 0; j < nums2.size(); j++) {
addedge(i, 500 + j, 1, nums1[i] ^ nums2[j]);
}
}
for (int i = 0; i < nums1.size(); i++) {
addedge(1000, i, 1, 0);
}
for (int i = 0; i < nums2.size(); i++) {
addedge(500 + i, 1001, 1, 0);
}
int max_flow = mcmf(1000, 1001);
return min_cost;
}
};
例题: 数组的最大与和
建图:
- 从源点连容量为1, 费用为0的边到每一个数字
- 从每个篮子连容量为2, 费用为0的边到汇点
- 从每个数字nums[i]向每个篮子j连容量为, 费用为-(nums[i]&j), 取负号是因为我们想求最大费用最大流, 而模板是求最小费用最大流, 所以取负号套模板后, 将答案取相反数就是最大费用
- 此时的最大流就是进入篮子的数字的个数, 最大费用就是这个过程中产生的最大与和
#define N 5000
#define M 100000
class Solution {
public:
int INF = 0x3f3f3f3f;
int n, m, tot = 1, lnk[N], cur[N], ter[M], nxt[M], cap[M], cost[M], dis[N], ret;
bool vis[N];
void add(int u, int v, int w, int c) {
ter[++tot] = v, nxt[tot] = lnk[u], lnk[u] = tot, cap[tot] = w, cost[tot] = c;
}
void addedge(int u, int v, int w, int c) { add(u, v, w, c), add(v, u, 0, -c); }
bool spfa(int s, int t) {
memset(dis, 0x3f, sizeof(dis));
memcpy(cur, lnk, sizeof(lnk));
std::queue<int> q;
q.push(s), dis[s] = 0, vis[s] = 1;
while (!q.empty()) {
int u = q.front();
q.pop(), vis[u] = 0;
for (int i = lnk[u]; i; i = nxt[i]) {
int v = ter[i];
if (cap[i] && dis[v] > dis[u] + cost[i]) {
dis[v] = dis[u] + cost[i];
if (!vis[v]) q.push(v), vis[v] = 1;
}
}
}
return dis[t] != INF;
}
int dfs(int u, int t, int flow) {
if (u == t) return flow;
vis[u] = 1;
int ans = 0;
for (int &i = cur[u]; i && ans < flow; i = nxt[i]) {
int v = ter[i];
if (!vis[v] && cap[i] && dis[v] == dis[u] + cost[i]) {
int x = dfs(v, t, std::min(cap[i], flow - ans));
if (x) ret += x * cost[i], cap[i] -= x, cap[i ^ 1] += x, ans += x;
}
}
vis[u] = 0;
return ans;
}
int mcmf(int s, int t) {
int ans = 0;
while (spfa(s, t)) {
int x;
while ((x = dfs(s, t, INF))) ans += x;
}
return ans;
}
int maximumANDSum(vector<int>& nums, int n) {
int s = 1001, t = 1002;
for (int i = 0; i < nums.size(); i++) {
addedge(s, i, 1, 0);
}
for (int i = 0; i < n; i++) {
addedge(500 + i + 1, t, 2, 0);
}
for (int i = 0; i < nums.size(); i++) {
for (int j = 0; j < n; j++) {
addedge(i, 500 + j + 1, 100000, -(nums[i] & (j + 1)));
}
}
int ans = mcmf(s, t);
return -ret;
}
};