费用流略解

· · 个人记录

费用流

极好体验

前置知识:网络流

这里讲最小费用最大流 ( Minimum Cost Maximum Flow, MCMF )

定义

在网络流的基础上,每条边有一个单位费用,每条边的费用是它的流量乘上单位花费。我们用 (u,v,w,c) 来表示从 u 到 v 最大流量为 w 单位费用为 c 的一条边。

最小费用最大流问题就是要找到一个最大流,使得在流量最大的基础上,它的所有边费用和最小。

方法

讲两种:SSP 与 Primal-Dual

zkw流我不会所以不讲

SSP

非常非常非常简单

在 Dinic 的基础上,贪心沿着费用最小路增广。

注意建反向边时要把费用取相反数。

Primal-Dual

以下所有 w_{ij} 指边 (i,j) 的费用

原始对偶,好写好调跑得快,就是不知道名字啥意思。

因为反向边的存在,我们不得不面对负权边,所以我们不得不使用死去的 SPFA。但是 SPFA 慢,我们要用 Dijkstra!

为了使边权非负,我们考虑给每条边增减边权,我们想到给每一个点一个势函数 h_i,每条边的边权变为 w^\prime_{ij}=w_{ij}+h_i-h_j。

令 G 指每条边 (i,j) 权值为 w_{ij} 的图,G^\prime 指每条边 (i,j) 权值为 w^\prime_{ij} 的图,然后我们得到 G^\prime 中路径 i_1,i_2,\cdots,i_q 长度为

\begin{aligned} &w^\prime_{i_1,i_2}+w^\prime_{i_2,i_3}+\cdots+w^\prime_{i_{q-1},i_q} \\=&w_{i_1,i_2}+h_{i_1}-h_{i_2}+w_{i_2,i_3}+h_{i_2}-h_{i_3}+\cdots+w_{i_{q-1},i_q}+h_{i_{q-1}}-h_{i_q}\\ =&w_{i_1,i_2}+w_{i_2,i_3}+\cdots+w_{i_{q-1},i_q}+h_{i_1}-h_{i_q} \end{aligned} 接下来是如何维护势函数。我们发现若 $d_i$ 为 $G$ 中 $s$ 到 $i$ 最短路长度,则有 $d_j\le d_i+w_{ij}$,变形一下就是 $w_{ij}+d_i-d_j\ge0$,因此 $d$ 就可以作为初始的势函数 $h$。 然后每次跑 `Dijkstra` 得到的 $G$ 中的距离 $d_i$ 作为新的 $h$ 即可。 **划重点**:`Dijkstra` 出来的是 $G^\prime$ 的距离 $d^\prime_i$,而 $d_i=d^\prime_i+h_s-h_i=d^\prime_i-h_i$,所以 $h_i+d^\prime_i=d_i$,所以代码里是累加。 ```cpp // P3381 Luogu // Graph Theory #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<queue> using namespace std; typedef long long ll; namespace cflow{ // 奇异的空行充满了代码, 112 行 const int maxn=5005,maxm=50005; const ll inf=0x3f3f3f3f3f3f3f3f; int n,s,t; inline int oth(int x){ return ((x-1)^1)+1; } struct edge{ int u,v; ll w,c; int nxt; edge(){} edge(int u_,int v_,ll w_,ll c_,int nxt_) { u=u_,v=v_,w=w_,c=c_,nxt=nxt_; } }e[2*maxm]; int fst[maxn],tp=0; void addedge(int u,int v,ll w,ll c){ e[++tp]=edge(u,v,w,c,fst[u]); fst[u]=tp; } void addflow(int u,int v,ll w,ll c){ addedge(u,v,w,c); addedge(v,u,0,-c); } ll dis[maxn],h[maxn],mxw[maxn];int cnt[maxn],frm[maxn],fre[maxn]; bool vis[maxn]; void spfa(){ static queue<int> q; while(!q.empty()){ q.pop(); } memset(dis,0x3f,sizeof(dis)); memset(cnt,0,sizeof(cnt)); memset(vis,0,sizeof(vis)); dis[s]=0;vis[s]=1;q.push(s);cnt[s]++; while(!q.empty()){ int u=q.front();q.pop();vis[u]=0; if(cnt[u]>n+1){ break; } for(int i=fst[u];i!=0;i=e[i].nxt){ int v=e[i].v; ll c=e[i].c; if(e[i].w==0){ continue; } if(dis[v]>dis[u]+c){ dis[v]=dis[u]+c; if(!vis[v]){ vis[v]=1;q.push(v);cnt[v]++; } } } } } void clear(){ memset(cflow::fst,0,sizeof(cflow::fst));cflow::tp=0; } void init(int n_,int s_,int t_){ n=n_,s=s_,t=t_; } struct pr{ ll dis;int u;pr(){}pr(int u_,ll dis_){u=u_,dis=dis_;} }; bool operator<(pr a,pr b){ return (a.dis!=b.dis)?(a.dis>b.dis):(a.u>b.u); } bool dijkstra(){ static priority_queue<pr> q; while(!q.empty()){ q.pop(); } memset(dis,0x3f,sizeof(dis)); dis[s]=0;mxw[s]=inf;q.push(pr(s,0)); while(!q.empty()){ int u=q.top().u;q.pop(); for(int i=fst[u];i!=0;i=e[i].nxt){ int v=e[i].v; ll c=e[i].c+h[u]-h[v],w=e[i].w; if(w && (dis[v]>dis[u]+c)){ dis[v]=dis[u]+c; mxw[v]=min(mxw[u],w); frm[v]=u; fre[v]=i; q.push(pr(v,dis[v])); } } } return dis[t]!=inf; } ll mincost,maxflow; void MCMF(){ spfa(); memcpy(h,dis,sizeof(h)); mincost=maxflow=0; while(dijkstra()){ ll flow=mxw[t]; maxflow+=flow; mincost+=flow*(dis[t]-h[s]+h[t]); for(int i=t;i!=s;i=frm[i]){ e[fre[i]].w-=flow; e[oth(fre[i])].w+=flow; } for(int i=1;i<=n;i++){ h[i]+=dis[i]; // 敲重点,累加 } } } } int n,m,s,t; int main(){ scanf("%d%d%d%d",&n,&m,&s,&t); cflow::n=n; cflow::s=s; cflow::t=t; for(int i=1;i<=m;i++){ int u,v;ll w,c; scanf("%d%d%lld%lld",&u,&v,&w,&c); if(c<0){ cflow::addflow(v,u,w,-c); }else{ cflow::addflow(u,v,w,c); } } cflow::MCMF(); printf("%lld %lld\n",cflow::maxflow,cflow::mincost); return 0; } ```