简介
做网络流最大流的题,常用的算法就是Dinic's algorithm。时间复杂度为,通常由于出题人水平较低,几乎能过所有的题。功利地看,这样就没问题了。但是,站在追求真(zhuang)理(B)的角度上。我们有理由去了解更多的算法。
事实上网络流最大流算法,(在具有正确性的前提下,)大体分两类:增广路(augmenting path)算法和推进-重标号(push-relabel)算法(又称预流推进(preflow-push)算法,括号外面的是我自己翻译的)。前一类很常用,常见的同时也是比较优秀的有Edmonds-Karp Algorithm()和Dinic's algorithm()。后类个体之间的区别在于优化方式,普通的做法复杂度达到了,队列优化(此种优化后的算法又称HLPP,Highest label preflow push algorithm 最高标号预流推进算法)后是,优先队列优化后是,还有一种动态树优化,时间复杂度达到了,可以说是非常优秀了。还有一些算法如KRT's algorithm,资料不是太多,我也不会。感兴趣的可以翻阅资料。
这篇文章主要说一下普通的最大流推进-重标号算法(General push-relabel maximum flow algorithm)用优先队列的优化,因为优化前后区别很小,理解难度一般。
算法
不同于增广路算法,这类算法是对网络流的一个模拟。在这里,我们用手来操控水,钦定它们的流向。为避免环的出现(会有死循环),我们在三维的空间里想象一下,将每个节点变成一个水坑,在平置的情况下,水显然是静止的。那推进的方法,就是我们钦定出每个点的高度,模拟一下水就好了。这里需要强调的是,水只能一层一层地流,即很平缓地流,也就是路程里面不能出现类似于瀑布的陡峭的山崖(steep arc,纯属意译)。遵循顺序来说,源点应是最高的,汇点应是最低的,故我们令源点高度为n(点个数)。
这种算法和其它的都类似,有一个试的过程,但是,怎么知道你试错了呢?怎么改正呢?首先,就要知道错了会怎样。第一个错误就是走错路,这里依旧使用增广路的想法(逃不过的)。你去了多少,反向边流量就加多少,该去的就算是绕一圈也能回来。第二个错误就是高度的设置,因为你是钦定的,总可能有问题。高度不对出现的问题就在于:水跑到山谷里去了,前面的边又往上走,形成了堰塞湖。尽管你可以钦定高度,但你不能钦定题目。这个的解决方法即是改。怎么改呢?形成了湖的地方地势较低,我们把它抬上去就好了,抬到刚刚好能往外流的地方。所以初始的点的高度(除源点)为0。这里有个小优化,你要找需要抬到的地方又需要循环一次,怪麻烦。直接发现有问题,一次抬一格,再走到这里就再看,需要就抬。尽管复杂度不变,但编程的难度减小了些。这时又会有个问题,万一你把它抬起来,它原来所处的平面就没有其它点了,这个水不也断流了么。即使下面有点,你高差太大,就不行了。所以我们记录每个平面上点的个数,如果安排过后出现这种情况,我们把其它在它上面的点直接举到n+1的高度,就能倒流回源点了(可以证明出现这种情况,那些点也没用了)。
基础的做法就是每次枚举每个点,显然复杂度较高,我们用一个优先队列来存被推过水的点。又可以证明,当每次最先弄高度最高的点是最快的;反之,弄最低的是最慢的。这两种时间复杂度为和。
代码
有一些细节问题并不好表述或是我懒得表述。请参见代码,有注释。题目参见:Luogu3376。
#include
using namespace std;
const int inf=0x3f3f3f3f;
int n,m;//The number of points & edges
int s,t;
int first[100005],nex[1000005],cap[1000005],to[1000005],tot=1;
// first edge of a point
// next edge of a edge
// capacity of a edge
// the ending to a edge
// cnt of a edge
long long flow[100005];//the remaining water in a point
int cnt[100005];//count of heights
int h[100005];//heights
struct node
{
int i,h;//The point & the height of the point
node(int i=0,int h=0):i(i),h(h){}
bool operator < (const node &a) const { return h q;//the priority queue of active nodes
void add_edge(int u,int v,int w)
{
tot++;
nex[tot]=first[u];
first[u]=tot;
to[tot]=v;
cap[tot]=w;
tot++;
nex[tot]=first[v];
first[v]=tot;
to[tot]=u;
cap[tot]=0;
return;
}
//Push some water from u to v through e.
int push(int u,int v,int e)
{
int delta=min((long long)cap[e],flow[u]);//'some water'
cap[e]-=delta;
cap[e^1]+=delta;
flow[u]-=delta;
flow[v]+=delta;
return delta;
}
long long maxflow()
{
//initialisation
while(!q.empty())q.pop();
memset(flow,0,sizeof(flow));memset(h,0,sizeof(h));memset(cnt,0,sizeof(cnt));
//push the s
h[s]=n;
//Set the usable water in s infinitive
flow[s]=inf;
q.push(node(s,h[s]));
//Do the push operations.
while(!q.empty())
{
//Get the point. Pop the top.
int u=q.top().i;
q.pop();
//Make sure there's remaining water.
if(!flow[u])
continue;
for(int e=first[u],v=to[e];e;e=nex[e],v=to[e])
{
// Make sure the arc is valid
if((u==s||h[u]==h[v]+1)
// Push some water and make sure the pushed isn't null.
&&push(u,v,e)
// You can't push the s/t into the queue, or you'll err.
&&v!=s&&v!=t)
// Push the next one into the queue.
q.push(node(v,h[v]));
}
// If there's remaining things in the point which means there's a lake,
if(u!=s&&u!=t&&flow[u])
{
// If the height is wrong,
if(!(--cnt[h[u]]))
{
// make all the inappropriate points' heights higher than s which are intouchable.
for(int i=1;i<=n;++i)
{
if(i!=s&&i!=t&&h[i]>h[u]&&h[i]<=n)h[i]=n+1;
}
}
++cnt[++h[u]];//Highten the point.
q.push(node(u,h[u]));//Push it into the queue again.
}
}
return flow[t];//The remaining water in the t is the maximum of flow.
}
int main()
{
ios::sync_with_stdio(0);//fasten the input/output
cin>>n>>m>>s>>t;
for(int i=0;i>u>>v>>w;
add_edge(u,v,w);
}
cout<
大家要相信它是最快的,是因为洛谷奇怪的数据才如此慢的。
其它
我觉得以后就写这个了。1复杂度低;2符合认知思维还短,没有奇怪的dfs、bfs。