Problem
poj.org/problem?id=1273
vjudge.net/contest/173005#problem/A
Meaning
一幅 n 条边、m 个点的有向图,每条边有个最大流量,1 号点是源点,m 号点是汇点,问从源点到汇点的最大总流量是多少。
Analysis
最大流模板题。记下几份最大流的模板。
Notes
正向边和反向边存在一起(在数组相邻位置),下标从 0 开始,则e
和e ^ 1
号边互为反向边(下标只有二进制最低位不同)
Code
Ford-Fulkerson 算法
- 深搜找增广路,很松的上界: O(FE) O ( F E ) (F 是最大流的值)
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 200, M = N, C = 10000000;
int head[M+1], to[N<<1], nxt[N<<1], cap[N<<1];
void add_edge(int f, int t, int c, int sz)
{
to[sz] = t;
cap[sz] = c;
nxt[sz] = head[f];
head[f] = sz;
}
bool vis[M+1];
// 深搜找增广路
int dfs(int now, int t, int minf)
{
if(now == t) // 到达汇点
return minf;
vis[now] = true;
for(int i = head[now]; ~i; i = nxt[i])
if(!vis[to[i]] && cap[i] > 0)
{
// 尝试从这里开始往下找增广路
int f = dfs(to[i], t, min(minf, cap[i]));
// 成功找到增广路
if(f > 0)
{
cap[i] -= f; // 正向边
cap[i ^ 1] += f; // 反向边
return f;
}
}
return 0;
}
int Ford_Fulkerson(int s, int t)
{
int flow = 0;
while(1)
{
memset(vis, false, sizeof vis);
// 尝试找增广路
int f = dfs(s, t, C);
if(!f) // 已不存在增广路
return flow;
// 增广
flow += f;
}
return flow;
}
int main()
{
int n, m;
while(~scanf("%d%d", &n, &m))
{
memset(head, -1, sizeof head);
for(int i = 0, s, e, c, sz = 0; i < n; ++i)
{
scanf("%d%d%d", &s, &e, &c);
add_edge(s, e, c, sz++);
add_edge(e, s, 0, sz++);
}
printf("%d\n", Ford_Fulkerson(1, m));
}
return 0;
}
Edmonds-Karp 算法
- 广搜找增广路, O(VE2) O ( V E 2 )
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 200, M = N, C = 10000000;
int head[M+1], from[N<<1], to[N<<1], nxt[N<<1], cap[N<<1];
void add_edge(int f, int t, int c, int sz)
{
from[sz] = f;
to[sz] = t;
cap[sz] = c;
nxt[sz] = head[f];
head[f] = sz;
}
int aug[M+1]; // 此次找增广路时,点 i 处能增广的流量
int pre[M+1]; // 增广路中,怼着点 i 的边(在前向星数组里)的下标
// 广搜找(最短?)增广路
int Edmonds_Karp(int s, int t)
{
int flow = 0;
while(1)
{
memset(aug, 0, sizeof aug);
// 队列装有可增广流量的点
// 可以从它们开始扩散找增广路
queue<int> que;
que.push(s);
aug[s] = C;
// 如果队列开在循环外(如全局的)
// 则每次在下面的循环前都要先清空
for(int tp; !que.empty(); que.pop())
{
tp = que.front();
for(int i = head[tp]; ~i; i = nxt[i])
{
// 如果 to[i] 点已被更新过
// 不管是否有更优,(此次)都不再更新
// 所以是找最短增广路?而不是最大增广流量?
if(!aug[to[i]] && cap[i] > 0)
{
// 怼着 to[i] 的是 i 这条边
pre[to[i]] = i;
// to[i] 可增广的流量受限于它的源头
aug[to[i]] = min(aug[tp], cap[i]);
que.push(to[i]);
}
}
if(aug[t]) // 此时队列不一定为空
break;
}
// 汇点无可增广流量
// 则没有增广路
if(!aug[t])
break;
// 从 t 回溯找增广路径
// 对于增广路上的边
// 正向的减去此次的增广流量
// 而反向的加上
for(int v = t; v != s; v = from[pre[v]])
{
cap[pre[v]] -= aug[t];
cap[pre[v] ^ 1] += aug[t];
}
// 增广
flow += aug[t];
}
return flow;
}
int main()
{
int n, m;
while(~scanf("%d%d", &n, &m))
{
memset(head, -1, sizeof head);
for(int i = 0, s, e, c, sz = 0; i < n; ++i)
{
scanf("%d%d%d", &s, &e, &c);
add_edge(s, e, c, sz++);
add_edge(e, s, 0, sz++);
}
printf("%d\n", Edmonds_Karp(1, m));
}
return 0;
}
Dinic 算法
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 200, M = N, C = 10000000;
int head[M+1], to[N<<1], nxt[N<<1], cap[N<<1];
void add_edge(int f, int t, int c, int sz)
{
to[sz] = t;
cap[sz] = c;
nxt[sz] = head[f];
head[f] = sz++;
}
int dis[M+1]; // 残余网络中,点到源点的距离
queue<int> que;
// 构建残余网络的分层图
// 就是给 dis 数组赋值
int bfs(int s, int t)
{
memset(dis, 0, sizeof dis);
dis[s] = 1;
que.push(s);
for(int tp; !que.empty(); que.pop())
{
tp = que.front();
for(int i = head[tp]; ~i; i = nxt[i])
if(!dis[to[i]] && cap[i] > 0)
{
dis[to[i]] = dis[tp] + 1;
que.push(to[i]);
}
}
return dis[t];
}
int cur[M+1]; // 当前弧优化
// aug 是到当前节点为止的可增广流量上限
int dfs(int now, int t, int aug)
{
if(now == t || !aug)
return aug;
int flow = 0, f;
// 从当前点开始扩散流量
// 每条边只考虑一次,之后就不在考虑,避免重算
// 所以要修改 cur[now]
for(int &i = cur[now]; ~i; i = nxt[i])
if(dis[to[i]] == dis[now] + 1 &&
(f = dfs(to[i], t, min(aug, cap[i]))) > 0)
{
cap[i] -= f;
cap[i ^ 1] += f;
flow += f; // 增广
aug -= f; // 减去已增广的量
if(!aug) // 当前点已无增广空间
break;
}
return flow;
}
int Dinic(int s, int t, int m)
{
int flow = 0;
// 残余网络中,汇点可达
// 则还有增广路
while(bfs(s, t))
{
// 初始化当前弧
for(int i = 1; i <= m; ++i)
cur[i] = head[i];
flow += dfs(s, t, C);
}
return flow;
}
int main()
{
int n, m;
while(~scanf("%d%d", &n, &m))
{
memset(head, -1, sizeof head);
for(int i = 0, s, e, c, sz = 0; i < n; ++i)
{
scanf("%d%d%d", &s, &e, &c);
add_edge(s, e, c, sz++);
add_edge(e, s, 0, sz++);
}
printf("%d\n", Dinic(1, m, m));
}
return 0;
}