2012-12-25
刚才把预流推进里的队列改成优先队列【手写堆,不会STL给跪了】,为啥速度变得更慢了QAQ测速用的POJ3469,普通预流推进2.7s,换成优先队列变成3.2s了不科学,求解答。
2012-12-28
改成STL的优先队列了,不过整体上的速度还是比SAP要慢,难道还有可以优化的地方么~求解
2013-01-02
我的sap是单路增广~今天看了一下多路增广但是不是很明白,目前单路增广的效率我已经很满意了,多路增广慢慢看看有没有必要用吧
2013-01-05
补充一下dinic的模板~不过写递归要小心栈溢出什么的
这两天把预流推进看了一下,今天完成了relabel to front的模板,不过还没有用优先队列实现最高标号推进什么的。
这里对求最大流的算法做一个总结。
最大流算法有增广路算法和预流推进算法两个常用的系列。理论时间复杂度一般是O(N*M^2)或O(N^2*M),但是最高标号预流推进的理论复杂度逆天到O(N^2*sqrt(M)),但是我没有实现也没有实测到底有多快。不过网络流的理论复杂度我完全不知道是怎么算的,实际速度也比理论要快上好多的样子。
先介绍增广路系列:
增广路系列的各种算法在于找增广路的方式不同,最早的FF据说是采用DFS找增广路,然后导致理论复杂度无法计算什么的。然后有了EK算法把FF的DFS换成了BFS,复杂度变为O(N*M^2),EK算法应该算的上是最简单易懂易写的最大流算法了吧,前些天有智能专业的妹子说开了算法课,然后说讲了网络流TAT【我们学校竟然还有这么神的专业】,然后说听不懂让我给讲><当时就随手写了一个EK【我会说那是我第一次写EK么】。唔~扯远了…………
然后就是可能大家最常用的dinic和sap了,这两种算法都是每次找最短增广路,所谓最短增广路,假设每条边的长度都是1,我们使用一个dis数组,dis[i]表示点i到汇点的最短路,对于每一条边u->v,只有满足dis[u]==dis[v]+1的才是允许弧,也就是说每一次找到的增广路上所有边都满足这个条件,当没有增广路时要重新计算dis,直到dis[s]>=n时算法结束,注意到每次重新标号都会导致这个点的dis不变或者增大,因为最短增广路里包含的边只会越来越多。黑书上说采用最短增广路可以把每次找增广路的平均时间复杂度从O(M)降到O(N),我表示没有找过相关的证明,这里就不对复杂度的计算做深入讨论了。dinic与sap的区别在于,dinic每次重新标号是直接对整个图进行BFS,sap则是每次对当前不能向下继续增广的点重新标号,因为我只用过没加优化的dinic,所以不好对dinic和sap的速度做比较,不过只看实现的话sap比dinic还是好写一些的。
因为sap是我最常用的,这里介绍一下sap的几个优化。
首先是gap优化,这个是对sap优化最大的,不过我没有测过不加gap优化的sap的速度到底如何,所以我也说不清这个优化到底有多明显。所谓gap优化是指gap[i]记录dis为i有点的个数,如果出现gap[i]==0的话直接结束,这时的求出的就是最大流,因为如果出现gap[i]==0就说明层次网络里出现了断层,不存在最短增广路。关于每个点的初始dis值,推荐在开始sap前先从汇点开始一次反向BFS,BFS过程的允许边是流量为0的边,也就是初始建图时每条边的反向边,虽然也可以将所有点的dis都设为0然后直接跑,但是其实这个BFS在多数情况下还是很有用的,尤其是在点比较多的情况下,这一次bfs可以明显减少sap过程中重新标号的次数,HDU4280那道平面图网络流【正解不是sap】我后来就直接用加了BFS的sap过掉的,不加BFS就超时……还有一个常数级别的当前弧优化,看很多人说这个优化的效果不明显,不过我还是说明一下,当前弧优化的原理是记录每一个点在当前情况下可以用于找增广路的第一条弧,然后从这条弧开始增广,当前弧之前的所有弧都不会出现在增广路中,直到当前点被重新标号为止。
然后是预流推进:
上面这个链接是DD神写的relabel to front相关。
预流推进也需要像sap一样先计算出dis,注意dis[源点]=n。首先把与源点相连的连都置为满流,对于每一个点用一个ef数组记录这个点积攒的流量,对于非源汇点ef[i]最终应该为0,这样用一个队列维护ef[i]!=0的点,对于边u->v,如果满足dis[u]==dis[v]+1那么将点u积攒的流量尽量压入点v,压入流量=min(ef[u],c[u][v])【c[u][v]是这条边的容量】,如果ef[v]!=0且点v不是源点和汇点时将点v加入队列,如果找不到这样的边就对点u重新标号,直到ef[u]==0,如果某个点的积攒流量无法向下压时最终都会回到源点上,这样当所有队列为空时ef[汇点]记录的就是最大流。
预流推进同样有gap优化,当gap[i]==0时将所有dis[u]>i 且n+1的点的dis置为n+1【n为图中点的数量】,这个优化是非常明显的,对于POJ3469那个题不加gap优化直接TLE,加了gap优化以后可以跑到2.7秒。如果某一层出现断层,那么比这一层更高的点的流量必然要都要被退回源点,不可能再继续向下推进,直接标记为n+1等待回推。
那个理论复杂度只有O(N^2*sqrt(M))的最高标号预流推进就是用一个优先队列每次取dis最大的点进行推进,完全不能理解为啥复杂度这么低><不会算复杂度的给跪了。
高标预流我按网上流传的一份吉大的模板写了一下结果比普通的预流推进还慢,果然还是要写优先队列呀啊啊啊。
这里先送上qinz的神SAP模板,跑起来相当快Qinz's SAP
接下来是我的模板:
先是SAP:【加满了所有优化】
const int N=100010;
const int M=400010;
const int inf=1<<30;
struct Edge{ int v,next,w; }e[M];
int head[N],cnt; int n,m,ans; int s,t; int pre[N],cur[N],dis[N],gap[N];
int q[N],open,tail;
void addedge(int u,int v,int w)
{
e[cnt].v=v; e[cnt].w=w; e[cnt].next=head[u]; head[u]=cnt++;
e[cnt].v=u; e[cnt].w=0; e[cnt].next=head[v]; head[v]=cnt++;
}
void BFS()
{
int i,u,v;
memset(gap,0,sizeof(gap)); memset(dis,-1,sizeof(dis));
open=tail=0; q[open]=t; dis[t]=0;
while (open<=tail)
{
u=q[open++];
for(i=head[u];i!=-1;i=e[i].next)
{
v=e[i].v;
if(e[i].w!=0 || dis[v]!=-1) continue;
q[++tail]=v; ++gap[dis[v]=dis[u]+1];
}
}
}
int sap(int n)
{
int i,v,u,flow=0,aug=inf;
int flag;
BFS(); gap[0]=1;
for (i=1;i<=n;i++) cur[i]=head[i];
u=pre[s]=s;
while (dis[s]<n)
{ flag=0;
for (int j=cur[u];j!=-1;j=e[j].next)
{ v=e[j].v;
if (e[j].w>0&&dis[u]==dis[v]+1)
{
flag=1; if (e[j].w<aug) aug=e[j].w; pre[v]=u; u=v;
if (u==t)
{
flow+=aug;
while (u!=s)
{ u=pre[u]; e[cur[u]].w-=aug; e[cur[u]^1].w+=aug; }
aug=inf;
}
break;
}
cur[u]=e[j].next;
}
if (flag) continue;
int mindis=n;
for (int j=head[u];j!=-1;j=e[j].next)
{
v=e[j].v;
if (e[j].w>0&&mindis>dis[v]) { mindis=dis[v]; cur[u]=j; }
}
if (--gap[dis[u]]==0) break;
++gap[dis[u]=mindis+1]; u=pre[u];
}
return flow;
}
然后是今天写好的预流推进:
#define N 210
#define M 40010
#define INF (1<<30)
int n,m,s,t;
int head[N],cnt,cur[N];
int dis[N],ef[N],gap[N];
struct edge{ int v,w,next; }e[M];
queue<int> q;
void addedge(int u,int v,int w)
{
e[cnt]=(edge){v,w,head[u]}; head[u]=cnt++;
e[cnt]=(edge){u,0,head[v]}; head[v]=cnt++;
}
void push(int u,int i)
{
int aug=min(e[i].w,ef[u]),v=e[i].v;
if(v!=s && v!=t && ef[v]==0) q.push(v);
e[i].w-=aug; e[i^1].w+=aug;
ef[u]-=aug; ef[v]+=aug;
}
void relabel(int u)
{
int mindis=INF,nl=dis[u];
--gap[dis[u]];
for(int i=head[u];i!=-1;i=e[i].next)
if(e[i].w!=0 && mindis>dis[e[i].v]) { cur[u]=i; mindis=dis[e[i].v]; }
++gap[dis[u]=mindis+1];
if(gap[nl]!=0 || nl>=n+1) return;
for(int i=1;i<=n;i++)
if(dis[i]>nl && dis[i]<=n && i!=s){ --gap[dis[i]]; ++gap[n+1]; dis[i]=n+1; }
}
void discharge(int u)
{
while(ef[u]>0)
{
if(cur[u]==-1) relabel(u);
int v=e[cur[u]].v,w=e[cur[u]].w;
if(w>0 && dis[u]==dis[v]+1) push(u,cur[u]);
cur[u]=e[cur[u]].next;
}
}
void BFS()
{
q.push(t); dis[t]=0;
while(! q.empty())
{
int u=q.front(); q.pop();
for(int i=head[u];i!=-1;i=e[i].next)
if(e[i].w==0 && dis[e[i].v]==-1 && e[i].v!=s)
{ dis[e[i].v]=dis[u]+1; q.push(e[i].v); }
}
}
int maxflow()
{
int i;
for(i=1;i<=n;i++) cur[i]=head[i],dis[i]=-1,ef[i]=gap[i]=0;
BFS(); dis[s]=n; ef[s]=INF;
for(i=1;i<=n;i++) if(dis[i]==-1) dis[i]=n;
for(i=1;i<=n;i++) gap[dis[i]]++;
for(i=head[s];i!=-1;i=e[i].next) push(s,i);
while(! q.empty())
{
int u=q.front();
q.pop();
discharge(u);
}
return ef[t];
}
dinic
#pragma comment(linker, "/STACK:32000000")
#define N 100010
#define M 200010
#define INF (1<<30)
int n,m,s,t,ans; int head[N],cnt,dis[N];
struct edge{ int v,w,next; }e[M];
void addedge(int u,int v,int w)
{ e[cnt].v=v; e[cnt].w=w; e[cnt].next=head[u]; head[u]=cnt++;
e[cnt].v=u; e[cnt].w=w; e[cnt].next=head[v]; head[v]=cnt++;
}
queue<int> q;
int bfs()
{
memset(dis,0,sizeof(dis)); dis[s]=1; q.push(s);
while (! q.empty())
{
int u=q.front(),v; q.pop();
for(int i=head[u];i!=-1;i=e[i].next)
if(e[i].w && dis[v=e[i].v]==0) { dis[v]=dis[u]+1; q.push(v); }
}
return dis[t];
}
int dfs(int s,int limit)
{
if(s==t) return limit; int v,tmp,cost=0;
for(int i=head[s];i!=-1;i=e[i].next)
if(e[i].w && dis[s]==dis[v=e[i].v]-1)
{
tmp=dfs(v,min(limit-cost,e[i].w));
if(tmp>0)
{ e[i].w-=tmp; e[i^1].w+=tmp; cost+=tmp; if(limit==cost) break;
}else dis[v]=-1;
}
return cost;
}
while (bfs()) ans0+=dfs(s,INF);