EK算法的改进版,不知道名字(也许是ZKW??)
现在知道名字了,原始对偶(Primal-Dual)
EK算法
EK算法就是不断的用SPFA寻找一条最小费用的增广路径,直到无法增广为止。
改进
类似于Dinic,先将结点用到汇点T的最短距离标号,每次只走dis[v]==dis[u]+cost[u->v]
的边进行增广,保证了费用最小。
在这种情况下,就可以类似Dinic,同时增广多条路径。
细节较多,见代码及注释
代码
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int INF=0x3F3F3F3F;
namespace CostFlow
{
const int MAX_NODE=505,MAX_EDGE=250005;
struct Edge
{
int v,cap,val;
Edge *nxt,*bck;
Edge(){}
Edge(int _v,int _c,int _val,Edge *n,Edge *b)
{v=_v;cap=_c;val=_val;nxt=n;bck=b;}
}edges[MAX_EDGE*2],*adj[MAX_NODE],*adj2[MAX_NODE],*ed_it=edges;
void AddEdge(int u,int v,int c,int val)
{
*ed_it=Edge(v,c,val,adj[u],ed_it+1);
adj[u]=ed_it;
ed_it++;
*ed_it=Edge(u,0,-val,adj[v],ed_it-1);
adj[v]=ed_it;
ed_it++;
}
deque<int> Q;
int dis[MAX_NODE];
bool inq[MAX_NODE],vis[MAX_NODE];
int value;
//dfs增广
int aug(int u,int T,int augco=0x3F3F3F3F)
{
if(u==T)
return augco;
int augc=augco;
vis[u]=true;
/*
由于存在反向边,费用为相反数
所以有可能通过反向边走回之前走过的路径
必须用vis数组记录走过的点,防止形成环
这一点与Dinic不同
*/
for(Edge *&e=adj2[u];e;e=e->nxt)//Dinic常规操作,用引用记录上一次枚举到的边
if(e->cap>0&&!vis[e->v]&&dis[e->v]==dis[u]-e->val)//判断dis标号
{
int delta=min(e->cap,augc);
delta=aug(e->v,T,delta);
e->cap-=delta;
e->bck->cap+=delta;
augc-=delta;
value+=delta*e->val;//这里记录费用
if(augc==0)
break;
}
vis[u]=false;
return augco-augc;//返回增广流量
}
bool relable(int S,int T)
{
memset(dis,0x3F,sizeof dis);
dis[T]=0;
Q.push_front(T);inq[T]=true;
while(!Q.empty())
{
int u=Q.front();
Q.pop_front();inq[u]=false;
for(Edge *e=adj[u];e;e=e->nxt)
if(e->bck->cap)
//因为是倒着走,但实际增广是正着走的
//所以这里应该判断反向边是否有流量
{
int v=e->v;
if(dis[v]>dis[u]-e->val)
//正向边的费用为反向边的相反数,所以为减号
{
dis[v]=dis[u]-e->val;
if(!inq[v])
{
//SLF优化(实际作用不大)
if(Q.empty()||dis[v]<dis[Q.front()])
Q.push_front(v);
else
Q.push_back(v);
inq[v]=true;
}
}
}
}
return dis[S]<INF;//S与T连通
}
void Solve(int S,int T,int &ans_flow,int &ans_val)
{
ans_flow=ans_val=0;
while(relable(S,T))//进行dis标号
{
memset(vis,0,sizeof vis);
memcpy(adj2,adj,sizeof adj);
ans_flow+=aug(S,T);//增广
}
ans_val=value;
}
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int u,v,c,w;
scanf("%d%d%d%d",&u,&v,&c,&w);
CostFlow::AddEdge(u,v,c,w);
}
int flow,val;
CostFlow::Solve(1,n,flow,val);
printf("%d\n",val);
return 0;
}