最小费用最大流略讲 \color{green}{\texttt{最小费用最大流略讲}} 最小费用最大流略讲
【定义】: \color{blue}{\texttt{【定义】:}} 【定义】:
-
单边费用:费用是图中的边的又一个边权。我们记为 b u , v b_{u,v} bu,v,它表示该边流过的单位流量的花费。什么叫单位流量的花费?即记流量为 f u , v f_{u,v} fu,v,则该边上的费用为 f u , v × b u , v f_{u,v} \times b_{u,v} fu,v×bu,v。
-
全图费用:整个图的费用就是所有边的费用的总和,即( E \texttt{E} E 为边集):
∑ u = 1 n ∑ v = 1 n f u , v × b u , v [ u ≠ v , ( u , v ) ∈ E ] \sum\limits_{u=1}^{n} \sum\limits_{v=1}^{n} f_{u,v} \times b_{u,v} [u \neq v,(u,v) \in \texttt{E}] u=1∑nv=1∑nfu,v×bu,v[u=v,(u,v)∈E]
-
算法目标:第一目标是让总流量最大,第二目标是让总花费最小。换成人话就是:在总流量最大的前提下让总花费最小。
【算法】: \color{blue}{\texttt{【算法】:}} 【算法】:
如何解决上面所述的问题呢?
仔细思考一下我们是如何进行
Dinic
\texttt{Dinic}
Dinic 算法的。我们使用了一个 bfs
算法算出每个点到起点的距离
dep
\text{dep}
dep。然后我们根据
dep
\text{dep}
dep 来保证我们增广的是最短路。
现在,我们只要把 bfs
算法换成 spfa
算法就可以了。我们只需把
b
b
b 作为花费跑一遍 spfa
算法,然后就做完了。
代码精讲 \color{green}{\texttt{代码精讲}} 代码精讲
-
首先,是整个算法最最重要的部分——
spfa
算法。 -
然后是如何更新答案?也很简单,我们在上面的代码中维护了两个非常重要的数组—— p r e pre pre 和 p a t h path path 数组。它们是什么意思呢? p r e u pre_u preu 表示在我们求出的最短路中, u u u 点是从 p r e u pre_u preu 这个点转移过来的。而 p a t h u path_u pathu 表示从 p r e u pre_u preu 点顺着边 p a t h u path_u pathu 转移到点 u u u。
我们考虑一下如何利用它们两个更新我们的数据。很简单,我们先从 t t t 顺着 p r e pre pre 一步步爬回 s s s,在爬的过程中,我们就可以求出本条路径可以容纳的最大流量 d d d。然后,我们让总流量 flow \texttt{flow} flow 加上 d d d,同时让 cost \texttt{cost} cost 加上 d i s t × d dis_t \times d dist×d。为什么是加上 d i s t × d dis_t \times d dist×d?因为 d i s t dis_t dist 就表示这条路径上的花费总和。因为 d d d 代表这条路径中所有的边都通过 d d d 的流量,所以总花费就增加了 d i s t × d dis_t \times d dist×d.
可能有人有这么一个问题:如何求 d d d?很简单,这条路径上所有的边的容量的最小值便是 d d d 的值。
-
最后一个问题:如何求出最终的答案。很简单,反复做如下事情:
- 调用
spfa_init()
函数求最短路。 - 调用
updata()
函数更新数据。 - 当
spfa_init()
返回false
时停止,否则回 1 反复。
- 调用
最后,给一道模板题(洛谷 P4016
)的代码给大家:
const int N=110,M=420;
struct edge{
int next,to,dis,cost;
}e[M<<1];int h[N],tot=1;
void add(int a,int b,int c,int d){
e[++tot]=(edge){h[a],b,c,d};h[a]=tot;
e[++tot]=(edge){h[b],a,0,-d};h[b]=tot;
}
int dis[N],pre[N],path[N];bool vis[N];
inline bool spfa_find(int s,int t){
memset(pre,-1,sizeof(pre));
memset(dis,127,sizeof(dis));
memset(vis,true,sizeof(vis));
queue<int> q;q.push(s);dis[s]=0;
while (!q.empty()){//开始spfa算法啦
int u=q.front();q.pop();vis[u]=true;
for(int i=h[u];i;i=e[i].next)
if (e[i].dis>0){//有更新的意义
register int to=e[i].to;
if (dis[to]>dis[u]+e[i].cost){
dis[to]=dis[u]+e[i].cost;
pre[to]=u;//从点u转移到to
path[to]=i;//转移到to的边
if (vis[to]){
q.push(to);
vis[to]=false;
}
}
}
}//spfa求以cost为边权的图的最短路
return pre[t]!=-1;//可以做到终点
}
int ans_cost,ans_flow,n,s,t,a[N],m;
inline void updata(int s,int t){
register int d=0x3f3f3f3f;
for(int i=t;i!=s;i=pre[i])
d=min(d,e[path[i]].dis);
// 求出我们本次可以获得的最大流量
ans_cost+=d*dis[t];//更新花费
ans_flow+=d; //更新流量
for(int i=t;i!=s;i=pre[i]){
e[path[i]].dis-=d;
e[path[i]^1].dis+=d;
}//更新边权(重要勿忘)
}
inline void calc_mincost_maxflow(){
while (spfa_find(s,t)) updata(s,t);
}//求最小费用最大流(函数名通过直译得)
int main(){
freopen("t1.in","r",stdin);
scanf("%d",&n);s=n+1;t=n+2;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
m+=a[i];//m:总和
}
for(int i=1;i<=n;i++)
a[i]-=m/n;//减去平均数
for(int i=1;i<=n;i++){
if (a[i]>0) add(s,i,a[i],0);
if (a[i]<0) add(i,t,-a[i],0);
}
for(int i=1;i<n;i++){
add(i+1,i,0x3f3f3f3f,1);
add(i,i+1,0x3f3f3f3f,1);
}//注意需要添加双向边
add(n,1,0x3f3f3f3f,1);
add(1,n,0x3f3f3f3f,1);
calc_mincost_maxflow();
printf("%d",ans_cost);
return 0;
}