网络流的一些基本名词概念:
弧的容量:指的是一条弧(有向边)最大承受能力。
弧的流量:实际通过这条弧的流量。
(流量和容量的概念一定要区分开。之后我们画的图里弧上的那个数字,指的是容量!!)
网络流:所有弧上流量的集合。
源点:网络指定的起始点
汇点:网络指定的终止点
可行流:简单的说就是一张图能够实现的网络流。(满足弧流量限制条件(小于等于弧容量)和平衡条件(源点的净流出量为总流量,汇点的净流入量为总流量))
可行流的流量:能够实现的网络流的流量。
最大流(网络最大流):流量最大的可行流。
零流:每条弧的流量都为零。
伪流(容量可行流):满足弧流量限制条件,不满足平衡条件。
饱和弧:流量等于容量。
非饱和弧:流量小于容量。
零流弧:流量等于零。
非零流弧:流量大于零。
链:顶点序列(u,a,b,c....,v)为一条链,链中的弧的方向不一定要求一致。因此一条链中有前向弧(其集合记作P+)和后向弧(其集合记作P-)。
前向弧:方向从u指向v的弧。
后向弧:方向从v指向u的弧。
前向弧和后向弧都是相对的,根据指定链的方向而决定。
增广路:假设P是一条从源点到汇点的链。P中所有的前向弧满足0<=f(u,v)<c(u,v),即P+不饱和;P中所有的后向弧满足0<f(u,v)<=c(u,v),即P-都是非零流弧。那么P就是一条增广路(增广路不一定是一条路径)。
层次网络:给每个点(用BFS)赋好它的所在层数,针对一个点我们只需要每次看它所指向的在下一层的点,这样的弧叫做允许弧。
下面用我的话说说“最大流Dinic算法”的思想:
其实就是求从源点最多能流出多少流量,能够到达汇点。
想象现在给你的图就是一张“水管图”,每条水管上都有一个数代表该条水管的容量,你现在就是要依次去找每一条增广路,看看这条路上的水管的最小容量是多少,那么就表示往这条路上最多可以通多少水(流量)!那么那些没有被灌满的管道(即这次通的流量<容量),还可以被灌水,所以标记的容量应该改为“原容量-当前流量”。然后就接着找下一条增广路了!直到所有增广路都找到了,就说明这些水管的流量之和即这张图的“最大流”!
需要get的点:
1.在建弧的时候要同时连一个反向弧,这样是让你能够反悔:
2. bfs更新层次是在每找完一条增广路之后都要更新,在代码中(可见下方代码部分):
if (e[i].c > 0 && d[v] == -1)
if条件:①这个边的容量仍>0。否则说明这条水管已经被灌满了或者说不能被灌。②这个点的层次还没赋(相当于bfs的visit数组)
return (d[T] != -1);
如果return false说明终点的层次赋不了,意思就是没有增广路了。
3. dfs的flow和res参数
flow表示之前的边里面的最小容量(最大流量)。flow减去你之后所能分走的最大流量(由递归求)来看还可不可以从这个节点再分流量出去。
res表示当前节点及之后节点中的最小容量(最大流量),返回给上一个节点用(想递归过程)。
炸点优化:如果res==0表示我当前点出去是流不到终点的,那么就不要走我了,把我的层次值赋为-1。(能保证在这条增广路的寻找中是不会再走你了)
4. 牛逼的e[i^1]
我从0开始存边(不能从1开始存边哟!),每次存一个正向边都会紧接着存它的反向边,这样的话 i^1就表示正(反)向边i的那个反(正)向边。因为与1异或,就是奇变偶,偶变奇的运算。
【模板代码】(其实dinic算法就是模板,网络流的题 更重要的是如何“建图”)
const int MAX_N = 100; // X 集合中的顶点数上限
const int MAX_M = 10000; // 总的边数上限
struct edge {
int v, c, next; // v 是指边的另一个顶点,c 表示容量
} e[MAX_M];
int p[MAX_N], eid;
void init() {
memset(p, -1, sizeof(p));
eid = 0;
}
void insert(int u, int v, int c) { // 插入一条从 u 连向 v,容量为 c 的弧
e[eid].v = v;
e[eid].c = c;
e[eid].next = p[u];
p[u] = eid++;
}
void addedge(int u, int v, int c) { // 用 insert2 来插入网络中的弧
insert(u, v, c);
insert(v, u, 0); // 插入一条方向相反、当前容量为 0 的弧
}
int S, T; // S 是源点,T 是汇点
int d[MAX_N]; // 存储每个顶点的层次
bool bfs() {
memset(d, -1, sizeof(d));
queue<int> q;
q.push(S);
d[S] = 0;
while (q.empty()==false) {
int u = q.front();
q.pop();
for (int i = p[u]; i != -1; i = e[i].next) {
int v = e[i].v;
if (e[i].c > 0 && d[v] == -1) {
q.push(v);
d[v] = d[u] + 1;
}
}
}
return (d[T] != -1);
}
int dfs(int u, int flow) { // flow 表示当前搜索分支的流量上限
if (u == T) {
return flow;
}
int res = 0;
for (int i = p[u]; i != -1; i = e[i].next) {
int v = e[i].v;
if (e[i].c > 0 && d[u] + 1 == d[v]) {
int tmp = dfs(v, min(flow, e[i].c)); // 递归计算顶点 v,用 c(u, v) 来更新当前流量上限
flow -= tmp;
e[i].c -= tmp;
res += tmp;
e[i ^ 1].c += tmp; // 修改反向弧的容量
if (flow == 0) { // 流量达到上限,不必继续搜索了
break;
}
}
}
if (res == 0) { // 当前没有经过顶点 u 的可行流,不再搜索顶点 u
d[u] = -1;
}
return res;
}
int maxflow() { // 函数返回值就是最大流的结果
int res = 0;
while (bfs()) {
res += dfs(S, INF); // 初始流量上限为 INF
}
return res;
}
巩固理解:
dfs(int s,int flow)的意思是在这条增广路上,到达s点之前的边所更新出的最大流量(最小容量)是flow,而dfs的返回值res是s点及s点之后的边所更新出的最大流量(最小容量)。那么dfs(0,0x3f3f3f3f)就是指0点及0点之后找到的一条增广路的最大流量。
I have so much to learn........Come on!!!