类似之前的储存方法,但稍作修改,代码如下:
struct Edge {
int from, to, cap, flow;
};
这样储存的是一条边。在做题的过程中,发现一个技巧(也算是技巧吧),出现无向边时,不需要加两条边,而是直接将反向边的容量也改为cap,会减少很多多余的时间和空间。
插入边的过程与之前相同:
void AddEdge(int from, int to, int cap) {
edges.push_back((Edge) {
from, to, cap, 0
});
edges.push_back((Edge) {
to, from, 0, 0
});
m = edges.size();
G[from].push_back(m - 2);
G[to].push_back(m - 1);
}
Dinic算法中,主要的操作过程如下:通过BFS构造层次图,然后DFS一次增广。
首先先来看看层次图。
在残量网络中,s到节点v的距离为d,那么d就是节点v的层次。只保留每个点到下一个层次的弧,也就是d(u) + 1 = d(v),的图,就是层次图。
层次图上的任意一条路径都是从s到层次1到层次2……直到t,可以发现,每一条这样的路径都是一条s-t最短路,这样求,不会出现走多余的边的情况。
如图,这样的一个图就是一个层次图。
从1开始,2和3都是层次1,4和5是层次2,6是层次3
在层次图上DFS求最大流自然很简单,就是不考虑反向边时能得到的最大流,多次增广后,重新计算层次图,发现s和t不连通,就退出。
它的时间复杂度不会很高,相对Ek会快很多,最多只会跑n-1次DFS,因为每次DFS之后s到t的路径至少会少一条,也就是最大距离至少会增加1,而每一次跑DFS最多会用nm的时间,每一次最多遍历每一个节点,每一个节点遍历它所链接的所有边,所以最终时间复杂度为O(N^2*M),相对Ek来说会快很多。
实际上,理论数值所对应的数据几乎不会出现,通常并不会想理论值说的这么慢,只会更快。
而且,对于它,还有一个很重要的优化,对于DFS来说,要保存一个“当前所有弧的最小残量”,如果等于0,必然是不能增广的,找到一条路径直接返回它的值即可,不然会出现多路增广不会退出的情况,还需要多加一次再算增广量,必然会很慢。
还有,当前弧优化,对于一次DFS中,可能会重复访问一个节点,虽然可能性比较小,这时候,已经走过的边自然是不需要再走,那么用一个数组记录上一次这个点走了哪些连接它的边,再次访问时不再重复走,那么就会更快。
下面为代码:
struct Dinic {
int n, m, s, t;
vector < Edge > edges;
vector < int > G[MAXN];
bool vis[MAXN];
int d[MAXN];
int cur[MAXN];
void AddEdge(int from, int to, int cap) {
edges.push_back((Edge) {
from, to, cap, 0
});
edges.push_back((Edge) {
to, from, 0, 0
});
m = edges.size();
G[from].push_back(m - 2);
G[to].push_back(m - 1);
}
bool BFS() {
memset(vis, 0, sizeof vis);
queue < int > Q;
Q.push(s);
d[s] = 0;
vis[s] = 1;
while(!Q.empty()) {
int x = Q.front();
Q.pop();
for(int i = 0; i < G[x].size(); ++i) {
Edge& e = edges[G[x][i]];
if(!vis[e.to] && e.cap > e.flow) { //如果未曾访问过,而且这一条边处于残量网络中,那么计算层次
vis[e.to] = 1;
d[e.to] = d[x] + 1;
Q.push(e.to);
}
}
}
return vis[t];
}
int DFS(int x, int a) {
if(x == t || a == 0) { // 如果找到一条增广的路或无法继续增广,返回当前流量
return a;
}
int flow = 0, f;
for(int& i = cur[x]; i < G[x].size(); ++i) {
Edge& e = edges[G[x][i]];
if(d[x] + 1 == d[e.to] && (f = DFS(e.to, min(a, e.cap - e.flow))) > 0) { // 如果处于同一个层次且这一条路径还能增广
e.flow += f;
edges[G[x][i] ^ 1].flow -= f;
flow += f;
a -= f;//增广 注意当前流量a要减f
if(a == 0) {//如果a=0,那么不能继续增广,返回上一条路
break;
}
}
}
return flow;
}
int Maxflow(int s, int t) {
this -> s = s;
this -> t = t;
int flow = 0;
while(BFS()) { // 计算层次图 如果s-t不连通那么退出
memset(cur, 0, sizeof cur); // 初始化当前弧
flow += DFS(s, INF); // 计算最大流
}
return flow;
}
} dinic;
当然,这里的DFS用的是递归实现的,但是如果Dinic依然会超时,那么可以尝试将DFS写成迭代的过程,会提高一些速度,但是也会相应地提高代码量,不如直接使用ISAP,不仅更快,而且代码稍微少一些。