HDU 1532 Drainage Ditches
网络流、最大流问题入门,基础的增广路算法(EK),首先定义一下该问题,主要是一个有向图,有两个特殊点-源点和汇点,每条边上有一个容量,在满足两个条件:
- 为每条边输入一个流量,且流量小于等于容量
- 以流量来计,源点只有出度,汇点只有入度,其他点入度等于出度
的情况下,尽可能多地往图里面输入流量,求整个图所有边的流量之和的最大值。
该算法的思想就是建图以后,每次在图里面找一条从源点到汇点的能使总流量增大的路(简单路,没环),每次就找一条,然后循环不停地找直到找不到为止,需要每次找完以后更新边的流量。每次找路用BFS
,需要记录每个点的前驱点和当前进度的可行流量值,且保证每个点只记录一次当前可行的值。然后的问题是需要理解每条边的反方向的容量和流量的意义,首先所有边包括反方向的容量都不会变(反方向边容量都是0
),所有边包括反方向的流量初始也都是0
,需要变的是流量,正常边的流量加上相应数值,反方向边的流量减去相应数值。
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
using namespace std;
typedef long long LL;
#define oo 1e9
struct Edge
{
int from;
int to;
int cap;
int flow;
};
vector<Edge> ve;
vector<int> v[201];
int a[201]; // 存放从源点到当前点的可行流量值
int pre[201]; // 存放当前点的前驱边(前驱边在边表里的索引)
void add_edge(int f, int t, int c)
{
ve.push_back({ f, t, c, 0 });
ve.push_back({ t, f, 0, 0 });
v[f].push_back(ve.size() - 2);
v[t].push_back(ve.size() - 1); // 反向边,这两个互逆的边的下标可以通过和1异或来互相取得
}
void init(int t)
{
for (int i = 1; i <= t; i++)
v[i].clear();
ve.clear();
}
LL max_flow(int t)
{
LL flow = 0;
for (;;)
{
memset(a, 0, sizeof a);
queue<int> q;
q.push(1);
a[1] = oo; // 从源点开始的流量无限
for (; !q.empty();)
{
int x = q.front();
q.pop();
for (int i = 0; i < v[x].size(); i++)
{
Edge& e = ve[v[x][i]];
if (!a[e.to] && e.flow < e.cap) // 确保没访问过,找一条源点到汇点的简单路,不形成环
{
pre[e.to] = v[x][i]; // 存放前驱边
a[e.to] = min(a[x], e.cap - e.flow);
q.push(e.to);
}
}
if (a[t]) break; // 汇点流量值已被更新,说明已找到一条到汇点的路
}
if (!a[t]) break; // 整个BFS都到不了汇点,总流量值不可增加,结束。
for (int i = t; i != 1; i = ve[pre[i]].from) // 更新边上的流量,为下次找路作准备
{
ve[pre[i]].flow += a[t]; // 是刚刚找的路上的边(不一定是原图输入时的正向边)
ve[pre[i] ^ 1].flow -= a[t]; // 是刚刚找的路上的边的反向边(不一定是原图输入时的反向边)
} // 这里和1异或就是对二进制最末尾取反,正好0变成1,1变成0,2变成3,3变成2......
flow += a[t];
}
return flow;
}
int main()
{
int N, M; // N为边数,M为点数 1~M 源点1,汇点M
for (; cin >> N >> M;)
{
init(M);
int f, t, c;
for (int i = 0; i < N; i++)
{
cin >> f >> t >> c;
add_edge(f, t, c);
}
cout << max_flow(M) << endl;
}
return 0;
}