众所周知SPFA死了。WC2019的课件中给出了对SPFA的各种hack。
那么费用流也不能好好用SPFA了qwq。
首先我们知道有一个johnson最短路是可以对带负权的图进行改造使得它可以跑dijk。考虑借鉴这个算法。
假设一开始的图费用都是正的,我们就可以直接跑dijk。可是一次增广之后有些边反向了qwq。那么我们还是考虑维护一个势 。结论是每次 即可。证明:
代码:
#include<bits/stdc++.h>
#define LL long long
#define pli pair<LL,int>
using namespace std;
const int N=1e5+50;
const LL inf=0x3f3f3f3f3f3f3f3f;
int n,m,S,T,ver[N],edge[N],val[N],nxt[N],head[N],tot=1,mn[N],f[N];LL dis[N],h[N],ans1,ans2;bool vis[N];
greater<pli> cmp;
struct node{
pli a[N];int n;
int size(){return n;}
void push(pli x){a[n++]=x;push_heap(a,a+n,cmp);}
void pop(){pop_heap(a,a+n--,cmp);}
pli top(){return *a;}
}q;
int read(){
int x=0,c,f=1;
while(!isdigit(c=getchar()))c=='-'?f=-1:0;
while(isdigit(c))x=x*10+c-48,c=getchar();
return x;
}
void add(int x,int y,int z,int v){
ver[++tot]=y;edge[tot]=z;val[tot]=v;nxt[tot]=head[x];head[x]=tot;
ver[++tot]=x;edge[tot]=0;val[tot]=-v;nxt[tot]=head[y];head[y]=tot;
}
bool dijk(){
for(int i=1;i<=n;i++)h[i]+=dis[i];
memset(dis,0x3f,sizeof(long long)*(n+1));
memset(vis,0,n+1);
dis[S]=0;q.push(make_pair(0,S));mn[S]=1e9;
while(q.size()){
int x=q.top().second;q.pop();
if(vis[x])continue;
vis[x]=1;
for(int i=head[x],y;i;i=nxt[i]){
y=ver[i];
if(edge[i]&&dis[y]>dis[x]+val[i]+h[x]-h[y]){
dis[y]=dis[x]+val[i]+h[x]-h[y];
mn[y]=min(mn[x],edge[i]);f[y]=i;
q.push(make_pair(dis[y],y));
}
}
}
return dis[T]!=inf;
}
void dfs(){
int x=T,k=mn[T];ans1+=k;
for(;x!=S;x=ver[f[x]^1]){
edge[f[x]]-=k;edge[f[x]^1]+=k;
ans2+=1ll*k*val[f[x]];
}
}
int main(){
scanf("%d%d%d%d",&n,&m,&S,&T);
for(int i=1,x,y,z,v;i<=m;i++){
x=read();y=read();z=read();v=read();
add(x,y,z,v);
}
while(dijk())dfs();
printf("%lld %lld\n",ans1,ans2);
}
另:这份代码中的堆是手写的。比priority_queue快一倍。
常用的费用流算法复杂度都是假的,不过一般没人卡就是了。复杂度正确的费用流
为什么费用流不能在最短路图上多路增广?因为最短路图上仍然可能有环。故会导致不能使用当前弧优化,可能会如同爆搜。其实也是可以写的。打vis标记,出时撤回。