返回顶部

网络流模板

EK求最大流

\(O(nm^2)\), 一般可以处理\(1000\)~\(10000\)的网络
每次增广一条路径

#include <bits/stdc++.h>
using namespace std;

const int N = 1e3 + 10;
const int M = 2e4 + 10;
const int INF = 1e9;

int n, m, S, T;
struct edge
{
	int to, nxt, flow;
}line[M];
int fist[N], idx;
int d[N], pre[N];
bool st[N];

void add(int x, int y, int z)
{
	line[idx] = {y, fist[x], z};
	fist[x] = idx ++;
	line[idx] = {x, fist[y], 0};
	fist[y] = idx ++;	
}

bool bfs()
{
	queue<int> q;
	memset(st, 0, sizeof st);
	q.push(S), st[S] = 1, d[S] = INF;
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(int i = fist[u]; i != -1; i = line[i].nxt)
		{
			int v = line[i].to;
			if(!st[v] && line[i].flow)
			{
				st[v] = 1;
				d[v] = min(line[i].flow, d[u]);
				pre[v] = i;
				if(v == T) return 1;
				q.push(v);
			}
		} 
	}	
	return 0;
} 

int EK()
{
	int res = 0;
	while(bfs())
	{
		res += d[T];
		for(int i = T; i != S; i = line[pre[i] ^ 1].to)
			line[pre[i]].flow -= d[T],
			line[pre[i] ^ 1].flow += d[T];
	}
	return res;
} 

int main()
{
	scanf("%d%d%d%d", &n, &m, &S, &T);
	memset(fist, -1, sizeof fist);
	for(int i = 1; i <= m; ++ i)
	{
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		add(a, b, c);
	}
	printf("%d\n", EK());
	return 0; 
} 

Dinic求最大流/最小割

\(O(n^2m)\), 一般可以处理\(10000\)~\(100000\)的网络
每次尽可能多的增广路径
防止在环上陷入死循环,使用分层图,处理每个点和起点间的距离

#include <bits/stdc++.h>
using namespace std;

const int N = 1e4 + 10;
const int M = 2e5 + 10;
const int INF = 1e9;

int n, m, S, T;
struct Edge
{
	int to, nxt, flow;
}line[M];
int fist[N], idx;
int d[N], cur[N];

void add(int x, int y, int z)
{
	line[idx] = {y, fist[x], z};
	fist[x] = idx ++;
	line[idx] = {x, fist[y], 0};
	fist[y] = idx ++;
}

bool bfs()
{
	queue<int> q;
	memset(d, -1, sizeof d);	
	q.push(S), d[S] = 0, cur[S] = fist[S];
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(int i = fist[u]; i != -1; i = line[i].nxt)
		{
			int v = line[i].to;
			if(d[v] == -1 && line[i].flow)
			{
				d[v] = d[u] + 1;
				cur[v] = fist[v];
				if(v == T) return 1;
				q.push(v); 
			}
		}
	}
	return 0;
}

int find(int u, int limit)
{
	if(u == T) return limit;
	int flow = 0;
	for(int i = cur[u]; i != -1 && flow < limit; i = line[i].nxt)
	{
		cur[u] = i;
		int v = line[i].to;
		if(d[v] == d[u] + 1 && line[i].flow)
		{
			int t = find(v, min(line[i].flow, limit - flow));
			if(!t) d[v] = -1;
			line[i].flow -= t;
			line[i ^ 1].flow += t;
			flow += t;
		}
	}
	return flow;
}

int dinic()
{
	int res = 0, flow;
	while(bfs()) while(flow = find(S, INF)) res += flow;
	return res;
}

int main()
{
	scanf("%d%d%d%d", &n, &m, &S, &T);
	memset(fist, -1, sizeof fist);
	for(int i = 1; i <= m; ++ i)
	{
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		add(a, b, c);
	}
	printf("%d\n", dinic());
	return 0;
}

无源汇上下界可行流

每条边的容量变成容量上界-容量下界
\(c_1\) 为该点少流入的流量,\(c_2\) 为该点少流出的流量
\(c_1\) > \(c_2\) ,从源点向该点连一条容量为 \(c_1 - c_2\)的边
\(c_1\) < \(c_2\) ,从该点向汇点连一条容量为 \(c_2 - c_1\)的边

#include <bits/stdc++.h>
using namespace std;

const int N = 200 + 10;
const int M = (10200 + N) * 2;
const int INF = 1e9;

int n, m, S, T;
struct Edge
{
	int to, nxt, flow;
}line[M];
int fist[N], idx;
int cur[N], d[N];
int A[N], l[M];

void add(int x, int y, int lz, int uz)
{
	line[idx] = {y, fist[x], uz - lz};
	l[idx] = lz;
	fist[x] = idx ++;
	line[idx] = {x, fist[y], 0};
	fist[y] = idx ++;
}

bool bfs()
{
	queue<int> q;
	memset(d, -1, sizeof d);	
	q.push(S), d[S] = 0, cur[S] = fist[S];
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(int i = fist[u]; i != -1; i = line[i].nxt)
		{
			int v = line[i].to;
			if(d[v] == -1 && line[i].flow)
			{
				d[v] = d[u] + 1;
				cur[v] = fist[v];
				if(v == T) return 1;
				q.push(v);
			}
		} 
	}
	return 0;
}

int find(int u, int limit)
{
	if(u == T) return limit;
	int flow = 0;
	for(int i = cur[u]; i != -1 && flow < limit; i = line[i].nxt)
	{
		cur[u] = i;
		int v = line[i].to;
		if(d[v] == d[u] + 1 && line[i].flow)
		{
			int t = find(v, min(line[i].flow, limit - flow));
			if(!t) d[v] = -1;
			line[i].flow -= t;
			line[i ^ 1].flow += t;
			flow += t;
		}
	}
	return flow;
}

int dinic()
{
	int res = 0, flow;
	while(bfs()) while(flow = find(S, INF)) res += flow;
	return res;	
}

int main()
{
	scanf("%d%d", &n, &m);
	memset(fist, -1, sizeof fist);
	S = 0, T = n + 1;
	for(int i = 1; i <= m; ++ i)
	{
		int a, b, c, d;
		scanf("%d%d%d%d", &a, &b, &c, &d);
		add(a, b, c, d);
		A[b] += c, A[a] -= c;
	}
	int tot = 0;
	for(int i = 1; i <= n; ++ i)
		if(A[i] > 0) add(S, i, 0, A[i]), tot += A[i];
		else if(A[i] < 0) add(i, T, 0, -A[i]);
	if(dinic() != tot) puts("NO");
	else 
	{
		puts("YES");
		for(int i = 0; i < m * 2; i += 2)
			printf("%d\n", line[i ^ 1].flow + l[i]);
	} 
	return 0;
}  

有源汇上下界最大流

从汇点向源点连一条容量为 \(+\infty\) 的边,使得所有点流量守恒
按照无源汇上下界可行流的求法建立虚拟源点和汇点,求新图的最大流,判断是否满流
在残留网络上删去汇点到源点容量为 \(+\infty\) 的边, 跑一遍从源点到汇点的最大流
答案为原网络的可行流加上该最大流

#include <bits/stdc++.h>
using namespace std;

const int N = 200 + 10;
const int M = (10000 + N) * 2;
const int INF = 1e9; 

int n, m, S, T;
struct Edge
{
	int to, nxt, flow;
}line[M];
int fist[N], idx;
int d[N], cur[N], A[N];

void add(int x, int y, int z)
{
	line[idx] = {y, fist[x], z};
	fist[x] = idx ++;
	line[idx] = {x, fist[y], 0};
	fist[y] = idx ++;
}

bool bfs()
{
	queue<int> q;
	memset(d, -1, sizeof d);
	q.push(S), d[S] = 0, cur[S] = fist[S];
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(int i = fist[u]; i != -1; i = line[i].nxt)
		{
			int v = line[i].to;
			if(d[v] == -1 && line[i].flow)
			{
				d[v] = d[u] + 1;
				cur[v] = fist[v];
				if(v == T) return 1;
				q.push(v);  
			}
		} 
	}
	return 0;
}

int find(int u, int limit)
{
	if(u == T) return limit;
	int flow = 0;
	for(int i = cur[u]; i != -1 && flow < limit; i = line[i].nxt)
	{
		cur[u] = i;
		int v = line[i].to;
		if(d[v] == d[u] + 1 && line[i].flow)
		{
			int t = find(v, min(line[i].flow, limit - flow));
			if(!t) d[v] = -1;
			line[i].flow -= t;
			line[i ^ 1].flow += t;
			flow += t;
		}
	}
	return flow;
}

int dinic()
{
	int res = 0, flow;
	while(bfs()) while(flow = find(S, INF)) res += flow;
	return res;
}

int main()
{
	int s, t;
	scanf("%d%d%d%d", &n, &m, &s, &t);
	memset(fist, -1, sizeof fist);
	S = 0, T = n + 1;
	
	for(int i = 1; i <= m; ++ i)
	{
		int a, b, c, d;
		scanf("%d%d%d%d", &a, &b, &c, &d);
		add(a, b, d - c);
		A[a] -= c, A[b] += c; 
	} 
	
	int tot = 0;
	for(int i = 1; i <= n; ++ i)
		if(A[i] > 0) add(S, i, A[i]), tot += A[i];
		else if(A[i] < 0) add(i, T, -A[i]);
	
	add(t, s, INF);
	
	if(dinic() != tot) puts("No Solution"); 
	else 
	{
		int res = line[idx - 1].flow;
		S = s, T = t;
		line[idx - 1].flow = line[idx - 2].flow = 0;
		printf("%d\n", res + dinic());
	}
	return 0;
}

有源汇上下界最小流

和有源汇上下界最大流做法类似,最后在残留网络上跑一遍汇点到源点的最大流即可
答案为原网络可行流减去该最大流

#include <bits/stdc++.h>
using namespace std;

const int N = 50010;
const int M = (N + 125003) * 2;
const int INF = 0x3f3f3f3f;

int n, m, S, T;
struct Edge
{
	int to, nxt, flow;
}line[M];
int fist[N], idx;
int cur[N], d[N];
int A[N];

void add(int x, int y, int z)
{
	line[idx] = {y, fist[x], z};
	fist[x] = idx ++;
	line[idx] = {x, fist[y], 0};
	fist[y] = idx ++;
} 

bool bfs()
{
	queue<int> q;
	memset(d, -1, sizeof d);
	q.push(S), d[S] = 0, cur[S] = fist[S];
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(int i = fist[u]; i != -1; i = line[i].nxt)
		{
			int v = line[i].to;
			if(d[v] == -1 && line[i].flow)
			{
				d[v] = d[u] + 1;
				cur[v] = fist[v];
				if(v == T) return 1;
				q.push(v);
			}
		}
	}
	return 0;
}

int find(int u, int limit)
{
	if(u == T) return limit;
	int flow = 0;
	for(int i = cur[u]; i != -1 && flow < limit; i = line[i].nxt)
	{
		cur[u] = i;
		int v = line[i].to;
		if(d[v] == d[u] + 1 && line[i].flow)
		{
			int t = find(v, min(line[i].flow, limit - flow));
			if(!t) d[v] = -1;
			line[i].flow -= t;
			line[i ^ 1].flow += t;
			flow += t;
		}
	}
	return flow;
}

int dinic()
{
	int res = 0, flow;
	while(bfs()) while(flow = find(S, INF)) res += flow;
	return res; 
}

int main()
{
	int s, t;
	scanf("%d%d%d%d", &n, &m, &s, &t);
	S = 0, T = n + 1;
	memset(fist, -1, sizeof fist);
	
	for(int i = 1; i <= m; ++ i)
	{
		int a, b, c, d;
		scanf("%d%d%d%d", &a, &b, &c, &d);
		add(a, b, d - c);
		A[a] -= c, A[b] += c; 
	}
	
	int tot = 0;
	for(int i = 1; i <= n; ++ i)
		if(A[i] > 0) add(S, i, A[i]), tot += A[i];
		else if(A[i] < 0) add(i, T, -A[i]);
	
	add(t, s, INF);
	
	if(dinic() != tot) puts("No Solution");
	else 
	{
		int res = line[idx - 1].flow;
		S = t, T = s;
		line[idx - 1].flow = line[idx - 2].flow = 0;
		printf("%d\n", res - dinic());
	}
	return 0;
} 

多源汇最大流

建立一个超级源点,从超级源点向所有源点连一条容量为 \(+\infty\) 的边
建立一个超级汇点,从所有汇点向超级汇点连一条容量为 \(+\infty\) 的边

#include <bits/stdc++.h>
using namespace std;

const int N = 10010;
const int M = (100000 + N) * 2;
const int INF = 1e9;

int n, m, S, T;
struct Edge
{
	int to, nxt, flow; 
}line[M];
int fist[N], idx;
int cur[N], d[N];

void add(int x, int y, int z)
{
	line[idx] = {y, fist[x], z};
	fist[x] = idx ++;
	line[idx] = {x, fist[y], 0};
	fist[y] = idx ++;
}

bool bfs()
{
	queue<int> q;
	memset(d, -1, sizeof d);
	q.push(S), d[S] = 0, cur[S] = fist[S];
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(int i = fist[u]; i != -1; i = line[i].nxt)
		{
			int v = line[i].to;
			if(d[v] == -1 && line[i].flow)
			{
				d[v] = d[u] + 1;
				cur[v] = fist[v];
				if(v == T) return 1;
				q.push(v);
			}
		}
	}
	return 0;
}

int find(int u, int limit)
{
	if(u == T) return limit;
	int flow = 0;
	for(int i = cur[u]; i != -1 && flow < limit; i = line[i].nxt)
	{
		cur[u] = i;
		int v = line[i].to;
		if(d[v] == d[u] + 1 && line[i].flow)
		{
			int t = find(v, min(line[i].flow, limit - flow));
			if(!t) d[v] = -1;
			line[i].flow -= t;
			line[i ^ 1].flow += t;
			flow += t;
		}
	}
	return flow;
}

int dinic()
{
	int res = 0, flow;
	while(bfs()) while(flow = find(S, INF)) res += flow;
	return res;
}

int main()
{
	int sc, tc;
	scanf("%d%d%d%d", &n, &m, &sc, &tc);
	S = 0, T = n + 1;
	memset(fist, -1, sizeof fist);
	for(int i = 1; i <= sc; ++ i)
	{
		int x;
		scanf("%d", &x);
		add(S, x, INF);
	}
	for(int i = 1; i <= tc; ++ i)
	{
		int x; 
		scanf("%d", &x);
		add(x, T, INF);
	}
	for(int i = 1; i <= m; ++ i)
	{
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		add(a, b, c);
	}
	printf("%d\n", dinic());
	return 0;
} 

最小费用最大流

#include <bits/stdc++.h>
using namespace std;
 
const int N = 5000 + 10;
const int M = (50000 + 10) * 2;
const int INF = 1e9;

int n, m, S, T;
struct Edge
{
	int to, nxt, flow, w; 
}line[M];
int fist[N], idx;
int pre[N], incf[N], d[N];
bool st[N];

void add(int x, int y, int z, int w)
{
	line[idx] = {y, fist[x], z, w};
	fist[x] = idx ++;
	line[idx] = {x, fist[y], 0, -w};
	fist[y] = idx ++;
}

bool spfa()
{
	queue<int> q;
	memset(d, 0x3f, sizeof d);
	memset(incf, 0, sizeof incf);
	q.push(S), d[S] = 0, incf[S] = INF;
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		st[u] = 0;
		
		for(int i = fist[u]; i != -1; i = line[i].nxt)
		{
			int v = line[i].to;
			if(line[i].flow && d[v] > d[u] + line[i].w)
			{
				d[v] = d[u] + line[i].w;
				pre[v] = i;
				incf[v] = min(incf[u], line[i].flow);
				if(!st[v])
				{
					q.push(v);
					st[v] = 1;
				}
			}
		}
	}
	return incf[T] > 0;
}

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 = line[pre[i] ^ 1].to)
		{
			line[pre[i]].flow -= t;
			line[pre[i] ^ 1].flow += t;
		}
	}
}

int main()
{
	scanf("%d%d%d%d", &n, &m, &S, &T);
	memset(fist, -1, sizeof fist);
	for(int i = 1; i <= m; ++ i)
	{
		int a, b, c, d;
		scanf("%d%d%d%d", &a, &b, &c, &d);
		add(a, b, c, d);
	}
	
	int flow, cost;
	EK(flow, cost);
	printf("%d %d\n", flow, cost);
	
	return 0;
}
posted @ 2021-02-19 17:24  __October  阅读(74)  评论(0编辑  收藏  举报