[模板] 最小費用最大流
阿新 • • 發佈:2021-08-15
一、題目
二、解法
講一種勢能 \(\tt dijkstra\) 的做法(簡稱勢能演算法),因為 \(\tt spfa\) 在單次擴充套件的時候可能會被卡到 \(O(nm)\),而勢能 \(\tt dijkstra\) 的時間複雜度是嚴格的 \(O(m\log n)\),在一些擴充套件次數較小的毒瘤題中可能會用到。
勢能演算法的中心思路是化負權邊為非負權邊,我們設計勢能函式 \(h(x)\) 來調整邊權,這裡的勢能和物理中的定義是很相似的,也就是勢能變化之和初末位置有關,而和路徑無關。
令 \(w'(u,v)=w(u,v)+h(u)-h(v)\),不難發現最後跑出來的最短路要加上 \(h(t)-h(s)\)
關鍵是如何使得 \(w(u,v)+h(u)-h(v)\geq0\) 恆成立,設 \(d(u)\) 為 \(s\) 到 \(u\) 真正的最短路,設 \(dis(u)\) 為 \(s\) 到 \(u\) 通過轉化後的邊求出來的最短路,令 \(h(s)=0\),有 \(dis(u)+h(u)=d(u)\)
如果我們再每次做完最短路之後讓 \(h(u)\leftarrow d(u)\) 是滿足條件的,因為如果是原來的邊那麼顯然 \(d(u)-d(v)+w(u,v)\geq 0\),如果是新產生的取負的邊那麼 \(d(u)=d(v)+w,(w>0)\rightarrow d(u)-d(v)-w=0\),新的邊權就是 \(0\)
時間複雜度 \(O(flow\cdot m\log n)\)
#include <cstdio> #include <queue> using namespace std; const int M = 50005; const int inf = 0x3f3f3f3f; int read() { int x=0,f=1;char c; while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;} while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();} return x*f; } int n,m,s,t,tot,f[M],dis[M],fw[M],pre[M],lst[M],h[M]; struct edge { int v,f,c,next; }e[2*M]; struct node { int u,c; bool operator < (const node &b) const { return c>b.c; } }; void add(int u,int v,int F,int c) { e[++tot]=edge{v,F,c,f[u]},f[u]=tot; e[++tot]=edge{u,0,-c,f[v]},f[v]=tot; } int bfs() { for(int i=1;i<=n;i++) dis[i]=inf; fw[s]=inf;pre[s]=lst[s]=dis[s]=0; priority_queue<node> q; q.push(node{s,0}); while(!q.empty()) { node t=q.top();q.pop();int u=t.u; for(int i=f[u];i;i=e[i].next) { int v=e[i].v,c=h[u]-h[v]+e[i].c; if(dis[v]>dis[u]+c && e[i].f) { dis[v]=dis[u]+c; pre[v]=u;lst[v]=i; fw[v]=min(fw[u],e[i].f); q.push(node{v,dis[v]}); } } } return dis[t]<inf; } signed main() { n=read();m=read();s=read();t=read();tot=1; for(int i=1;i<=m;i++) { int u=read(),v=read(),f=read(),c=read(); add(u,v,f,c); } int ans=0,flow=0; while(bfs()) { int zy=t; flow+=fw[t]; ans+=(dis[t]+h[t])*fw[t]; while(zy!=s) { e[lst[zy]].f-=fw[t]; e[lst[zy]^1].f+=fw[t]; zy=pre[zy]; } for(int i=1;i<=n;i++) h[i]+=dis[i]; } printf("%d %d\n",flow,ans); }