-
單邊費用:費用是圖中的邊的又一個邊權。我們記爲 ,它表示該邊流過的單位流量的花費。什麼叫單位流量的花費?即記流量爲 ,則該邊上的費用爲 。
-
全圖費用:整個圖的費用就是所有邊的費用的總和,即( 爲邊集):
-
算法目標:第一目標是讓總流量最大,第二目標是讓總花費最小。換成人話就是:在總流量最大的前提下讓總花費最小。
如何解決上面所述的問題呢?
仔細思考一下我們是如何進行 算法的。我們使用了一個 bfs
算法算出每個點到起點的距離 。然後我們根據 來保證我們增廣的是最短路。
現在,我們只要把 bfs
算法換成 spfa
算法就可以了。我們只需把 作爲花費跑一遍 spfa
算法,然後就做完了。
-
首先,是整個算法最最重要的部分——
spfa
算法。 -
然後是如何更新答案?也很簡單,我們在上面的代碼中維護了兩個非常重要的數組—— 和 數組。它們是什麼意思呢? 表示在我們求出的最短路中, 點是從 這個點轉移過來的。而 表示從 點順着邊 轉移到點 。
我們考慮一下如何利用它們兩個更新我們的數據。很簡單,我們先從 順着 一步步爬回 ,在爬的過程中,我們就可以求出本條路徑可以容納的最大流量 。然後,我們讓總流量 加上 ,同時讓 加上 。爲什麼是加上 ?因爲 就表示這條路徑上的花費總和。因爲 代表這條路徑中所有的邊都通過 的流量,所以總花費就增加了 .
可能有人有這麼一個問題:如何求 ?很簡單,這條路徑上所有的邊的容量的最小值便是 的值。
-
最後一個問題:如何求出最終的答案。很簡單,反覆做如下事情:
- 調用
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;
}