给定一个有向图(若是无向图,那么可以用两条有向边表示一条无向边),每条边都有一个权值表示这条边上的流量,
问题:有n个城市,告诉你m条路以及路上最多分分钟的车流量。
然后我们求 从1 点压入无限多的车,问从n点 分分钟能跑出多少车量的问题称为 网络流(最大流)。
我们称无限多进入车辆的点为:源点
车辆的出口的点为:汇点
我们可以用如下结构体来记录信息:(这里用的是邻接表建图)
struct Edge
{
int from, to, cap, flow, next;
}edge[MAXM];
对应信息 分别为起点, 终点, 最大车流量,当前车流量,下一节点的指针。
对于每条边,这条边当前的车流量 = 最大车流量时,称这条边为:满流
对于每条边,cap - flow 就是残余流量(就是还能有多少量车可以从这条路上通过)
为了方便记录,这里定义一个head数组记录下一节点的指针(初始化为-1),用一个变量top来记录总边数。
注意代码中MAXN 为点数,MAXM为边数。
下面问题就是怎么记录信息,说白了就是怎么增边。
增边函数:
void addedge(int u, int v, int w)
{
Edge E1 = {u, v, w, 0, head[u]};//正向建边
edge[top] = E1;
head[u] = top++;
Edge E2 = {v, u, 0, 0, head[v]};//反向建
edge[top] = E2;
head[v] = top++;
}
核心思想:以每条边的最大流量 - 当前流量 为该边的权值,在残余网络(理解为当前网络也行)中查找源点到汇点的最短路径是否存在,若存在继续增广。
代码:
int dist[MAXN];//记录该节点到源点的距离
int cur[MAXN];//保存该节点正在考虑的弧,避免重复计算
int vis[MAXN];//标记该点是否已经存在于当前选择的路径中
bool BFS(int start, int end)
{
//我们从残余网络找一条从start->end的路径(每条边的边权为最大流量-当前流量)
//显然当找不到这样的路径时说明不能继续增加流量 则返回false 算法结束
memset(dist, -1, sizeof(dist));//初始化
memset(vis, 0, sizeof(vis));
queue<int> Q;
while(!Q.empty()) Q.pop();//清空队列
dist[start] = 0;//源点到汇点的距离为0
vis[start] = 1;//标记
Q.push(start);//进队列
while(!Q.empty())
{
int u = Q.front();
Q.pop();
for(int i = head[u]; i != -1; i = edge[i].next)//遍历 u 所指向的边
{
Edge E = edge[i];
if(!vis[E.to] && E.cap > E.flow)//当前路径可到达的点未标记 且这条路径没有满流
{
dist[E.to] = dist[u] + 1; //建立层次图
vis[E.to] = 1;//标记
if(E.to == end) return true;//找到路径
Q.push(E.to);//进队列
}
}
}
return false;//找不到一条可以从源点到汇点的最短路径
}
int DFS(int x, int a, int end)//把找到的这个路径上所有的边的当前流量都增加a(a是所找出路径的边中 残余流量的最小值)
{
if(x == end || a == 0) return a;//优化
int flow = 0, f;
for(int& i = cur[x]; i != -1; i = edge[i].next)//从上次考虑的弧开始
{
Edge& E = edge[i];
if(dist[E.to] == dist[x]+1 && (f = DFS(E.to, min(a, E.cap-E.flow), end)) > 0)//可继续增广
{
E.flow += f;//正向边
edge[i^1].flow -= f;//反向边
flow += f;//总流量 加上 f
a -= f;//最小可增流量 减去f
if(a == 0) break;
}
}
return flow;
}
int Maxflow(int start, int end)
{
int flow = 0;
while(BFS(start, end))//存在最短路径
{
memcpy(cur, head, sizeof(head));
flow += DFS(start, INF, end);//增广
}
return flow;
}
完整代码:源点为1,汇点为n
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#define MAXN 200+10
#define MAXM 500+10
#define INF 2000000000+10
using namespace std;
struct Edge
{
int from, to, cap, flow, next;
}edge[MAXM];
int dist[MAXN], vis[MAXN];//用于求最短路径
int top;//记录边的数目
int head[MAXN];//存储上一节点指针
int cur[MAXN];
int n, m;//点数 边数
void init()
{
top = 0;
memset(head, -1, sizeof(head));
}
void addedge(int u, int v, int w)
{
Edge E1 = {u, v, w, 0, head[u]};//正向建边
edge[top] = E1;
head[u] = top++;
Edge E2 = {v, u, 0, 0, head[v]};//反向建
edge[top] = E2;
head[v] = top++;
}
void getmap()
{
int a, b, d;
while(m--)
{
scanf("%d%d%d", &a, &b, &d);
addedge(a, b, d);//增边
}
}
bool BFS(int start, int end)
{
memset(dist, -1, sizeof(dist));//初始化
memset(vis, 0, sizeof(vis));
queue<int> Q;
while(!Q.empty()) Q.pop();//清空队列
dist[start] = 0;//自己到自己的距离为0
vis[start] = 1;//标记
Q.push(start);//进队列
while(!Q.empty())
{
int u = Q.front();
Q.pop();
for(int i = head[u]; i != -1; i = edge[i].next)//遍历 u 所指向的边
{
Edge E = edge[i];
if(!vis[E.to] && E.cap > E.flow)//当前路径可到达的点未标记 且这条路径没有满流
{
dist[E.to] = dist[u] + 1; //建立层次图
vis[E.to] = 1;//标记
if(E.to == end) return true;//找到路径
Q.push(E.to);//进队列
}
}
}
return false;//找不到一条可以从源点到汇点的最短路径
}
int DFS(int x, int a, int end)//把找到的这个路径上所有的边的当前流量都增加a(a是所找出路径的边中 残余流量的最小值)
{
if(x == end || a == 0) return a;//优化
int flow = 0, f;
for(int& i = cur[x]; i != -1; i = edge[i].next)//从上次考虑的弧开始
{
Edge& E = edge[i];
if(dist[E.to] == dist[x]+1 && (f = DFS(E.to, min(a, E.cap-E.flow), end)) > 0)//可继续增广
{
E.flow += f;//正向边
edge[i^1].flow -= f;//反向边
flow += f;//总流量 加上 f
a -= f;//最小可增流量 减去f
if(a == 0) break;
}
}
return flow;
}
int Maxflow(int start, int end)
{
int flow = 0;
while(BFS(start, end))//存在最短路径
{
memcpy(cur, head, sizeof(head));
flow += DFS(start, INF, end);//增广
}
return flow;
}
int main()
{
while(scanf("%d%d", &n, &m) != EOF)
{
init();//初始化
getmap();//建图
printf("%d\n", Maxflow(1, n));
}
return 0;
}
SAP模板:
const int MAXN = 100010;//点数的最大值
const int INF = 0x3f3f3f3f;
struct Edge {
int from, to, cap, next;
};
Edge edge[MAXN * 2];
int head[MAXN], edgenum;
void init() { edgenum = 0; memset(head, -1, sizeof(head)); }
void addEdge(int u, int v, int w) {
edge[edgenum].from = u;
edge[edgenum].to = v;
edge[edgenum].cap = w;
edge[edgenum].next = head[u];
head[u] = edgenum++;
}
int dep[MAXN];
int gap[MAXN];//gap[x]=y :说明残留网络中dep[i]==x的个数为y
int n;//n是总的点的个数,包括源点和汇点W
void BFS(int s,int t) {
memset(dep, -1, sizeof(dep)); memset(gap, 0, sizeof(gap)); gap[0]=1;
int que[MAXN];
int Front, rear;
Front = rear = 0; dep[t]=0;
que[rear++] = t;
while(Front != rear) {
int u = que[Front++];
if(Front == MAXN) Front = 0;
for(int i = head[u];i != -1;i = edge[i].next) {
int v = edge[i].to;
if(dep[v] != -1)continue;
que[rear++] = v;
if(rear == MAXN) rear = 0;
dep[v] = dep[u] + 1;
++gap[dep[v]];
}
}
}
int cur[MAXN];
int S[MAXN];
int SAP(int s,int t) {
int res = 0; BFS(s, t);
int top = 0;
memcpy(cur, head, sizeof(head));
int u = s; int i;
while(dep[s] < n) {
if(u == t) {
int temp = INF;
int inser;
for(i = 0; i < top; i++) {
if(temp > edge[S[i]].cap) {
temp = edge[S[i]].cap;
inser = i;
}
}
for(i = 0; i < top; i++) {
edge[S[i]].cap -= temp;
edge[S[i]^1].cap += temp;
}
res += temp;
top = inser;
u = edge[S[top]].from;
}
if(u != t && gap[dep[u]-1] == 0)//出现断层,无增广路
break;
for(i = cur[u];i != -1; i = edge[i].next) {
if(edge[i].cap != 0 && dep[u] == dep[edge[i].to] + 1)
break;
}
if(i != -1) {
cur[u] = i;
S[top++] = i;
u = edge[i].to;
}
else {
int Min = n;
for(i = head[u]; i != -1; i = edge[i].next) {
if(edge[i].cap == 0) continue;
if(Min > dep[edge[i].to]) {
Min = dep[edge[i].to];
cur[u] = i;
}
}
--gap[dep[u]];
dep[u] = Min+1;
++gap[dep[u]];
if(u != s) u = edge[S[--top]].from;
}
}
return res;
}