最小费用流
大致思路:
在寻找增广路的前提下,只找s到t距离最短的增广路,并沿着这条路进行增广。
本代码采用SPFA进行寻找最短的增广路,如果所有 cost为正的,则保证残余流量图中不会出现负环。
为什么不会出现负环? 如果出现负环,代表这个环有流量,且环中的边都是反向边( cost为负的)。且在此之前肯定沿着环的反方向进行增广了(当时肯定是正环) ,但是如果先前在一个正环上进行了一次增广就不会是沿着最短路增广,这与先前每次增广的都是最短路矛盾!故不可能存在负环。
时间复杂度为 O(∣V∣∗∣E∣2∗log2∣V∣) 这是上届,一般情况都能过。
其实在增广路进行寻找时 可以用Dijkstra算法优化(引入势的概念)
最小费用流多用于指派问题,比如二分图最小权匹配(二分图的网络流图,费用为权值即可),二分图最大权匹配(把费用变为权的负值即可),还有不重叠边的多路径选取的最小权(最大权)问题。
采用SPFA的寻找最短增广路算法:
#include<bits/stdc++.h>
#define mset(a,b) memset(a,b,sizeof(a))
using namespace std;
const int maxn=220;//顶点数量
const int inf=0x3f3f3f3f;
struct edge{
int to,cap,cost,rev;
edge(){}
edge(int to,int cap,int cost,int rev){this->to=to,this->cap=cap,this->cost=cost,this->rev=rev;}
};
class MCMF{
public:
vector<edge> adja[maxn];
int dis[maxn],prevv[maxn],preve[maxn],top;
bool inque[maxn];
void init(int n)
{
for(int i=0;i<n;++i) adja[i].clear();
top=n;
}
void addEdge(int u,int v,int f,int cost){
adja[u].push_back(edge(v,f,cost,adja[v].size()));
adja[v].push_back(edge(u,0,-1*cost,adja[u].size()-1));
}
bool spfa(int s,int t){
queue<int> mp;
mset(dis,inf);
mset(prevv,-1);
mset(inque,0);
mp.push(s),prevv[s]=s,dis[s]=0,inque[s]=true;
while(!mp.empty()){
int u=mp.front();
mp.pop();
inque[u]=false;
for(int i=0;i<adja[u].size();++i){
edge& e=adja[u][i];
if(e.cap>0&&dis[e.to]>dis[u]+e.cost){
dis[e.to]=dis[u]+e.cost;
prevv[e.to]=u;
preve[e.to]=i;
if(!inque[e.to]){
inque[e.to]=true;
mp.push(e.to);
}
}
}
}
if(~prevv[t]) return true;
return false;
}
int minCostMaxFlow(int s,int t,int &cost){//返回s到t的最大流, 并把花费存到cost里面
cost=0;
int flow=0;
while(spfa(s,t))
{
int minn=inf;
for(int v=t;v!=prevv[v];v=prevv[v]){
minn=min(adja[prevv[v]][preve[v]].cap,minn);
}
flow+=minn;
/*找出最小minn,进行增广 即flow+= cost+= 残余网络的更新*/
for(int v=t;v!=prevv[v];v=prevv[v]){
edge& e=adja[prevv[v]][preve[v]];
cost+=minn*e.cost;
e.cap-=minn;
adja[v][e.rev].cap+=minn;
}
}
return flow;
}
};
//他的第三个函数也可以这样写,即满足流f的最小花费
int minCostMaxFlow(int s,int t,int f) //不能满足流f则返回-1
{
int cost=0;
while(f>0)
{
spfa(s,t);
if(dis[t]==inf)
return -1;
int d=f;
for(int v=t; v!=prevv[v]; v=prevv[v]) //找到d
d=min(d,adja[prevv[v]][preve[v]].cap);
cost+=d*dis[t];
f-=d;
for(int v=t; v!=prevv[v]; v=prevv[v])
{
edge &e=adja[prevv[v]][preve[v]];
e.cap-=d;
adja[v][e.rev].cap+=d;
}
}
return cost;
}
###采用dijstra的最短增广路算法:(有时候性能不如SPFA的,可能是因为容器的问题)
证明:
每次求最短路时,用 h(v)作为顶点的势,其 h(v) 初始化为0,后来每一回合让 h(v)等于上一次 dijkstra来求最短路的 dist(v)。
让 w′(e)=w(e)+h(u)−h(v),其中e=u to v. 且w′(e)>=0 当e边存在时
用 w′(e)代替 w(e)以寻找一条 s到 t的最短路径
证明过程中关于我的一些问题以及解释:
下面关于术语可能不太准确,但能从语言中理解什么意思。(整理时候太过匆忙,请谅解)
- 在该顶点的势中 h[v]是上一次 s 到 v顶点的最短路,假设第一步中 w′(e)>=0且更新了 s到所有顶点的最短路。为什么下一步中下列条件能够满足?
1.图中所有存在的边e都满足这样的情况即 w′(e(u−>v))=w(e)+h(u)−h(v)>=0
1.如果上一次有 e这条边,那么就不难证明 w′(e)>=0
2.如果上一次没 e这条边,那么 h(v)和 h(u)的大小不能直接讨论,但是如果之后这条边出现,那么证明上次 e在 s到 t的最短路径中,即有 w‘(e)=w′(e)+h(u)−h(v)==0
所以如果上一次 w′(e)>=0,且经过了s到所有点的最短路径的扫描,那么下次的 w′(e)>=0 (如果边 e 出现的话)
2.为什么 w′(e)对应的图中s到t的最短路径一定是残余网络中 s到 t的最短路径?
在 w′(e)对应的图中, s到 t的任何路径 w′(s−>v)+w′(v−>u)+w′(u−>t)=w(s−>v)+w(v−>u)+w(u−>t)−h(t)
表示为对应w’(e)中相应的图的最短路减去t点的势, 故两个图的最短路等价。
注意事项:
如果第一次就存在负边,那么第一步用SPFA求出s到所有点的最短路径即可。其余步骤相同。
如果存在负圈就用bellman-ford算法找出负圈并在负圈上尽量增广把负圈消去 (消去其中一条边即可)
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int>P;//first保存最短距离,second保存顶点的编号
const int maxn=250;
const int INF=0x3f3f3f3f;
struct Edge
{
int to, cap, cost, rev;//终点,容量(指残量网络中的),费用,反向边编号
Edge(int t, int c, int cc, int r) :to(t), cap(c), cost(cc), rev(r) {}
};
class MCMF
{
public:
int V;//顶点数
vector<Edge>G[maxn];//图的邻接表
int h[maxn];//上一次顶点的最短路s到v的最短路径
int dist[maxn];//最短距离
int prevv[maxn];//最短路中的父结点
int preve[maxn];//最短路中的父边
void init(int n)
{
for(int i=0; i<n; ++i) G[i].clear();
V = n;
}
void addEdge(int from, int to, int cap, int cost)
{
G[from].push_back(Edge( to, cap, cost, G[to].size()));
G[to].push_back(Edge( from, 0, -cost, G[from].size() - 1 ));
}
int min_cost_flow(int s, int t, int f)//返满足流f的最小费用 不能满足返回-1
{
int res = 0;
fill(h, h + V, 0);
while (f>0)//f>0时还需要继续增广
{
priority_queue<P, vector<P>, greater<P> >q;
fill(dist, dist + V, INF);//距离初始化为INF
dist[s] = 0;
q.push(P(0, s));
while (!q.empty())
{
P p = q.top();
q.pop();
int v = p.second;
if (dist[v]<p.first) continue;//p.first是v入队列时候的值,dist[v]是目前的值,如果目前的更优,扔掉旧值
for (int i = 0; i<G[v].size(); i++)
{
Edge&e = G[v][i];
if (e.cap>0 && dist[e.to]>dist[v] + e.cost + h[v] - h[e.to])//松弛操作
{
dist[e.to] = dist[v] + e.cost + h[v] - h[e.to];
prevv[e.to] = v;//更新父结点
preve[e.to] = i;//更新父边编号
q.push(P(dist[e.to], e.to));
}
}
}
if (dist[t] == INF)//如果dist[t]还是初始时候的INF,那么说明s-t不连通,不能再增广了
return -1;
for (int j = 0; j<V; j++)//更新h
h[j] = dist[j];
int d = f;
int sum=0;
for (int x = t; x != s; x = prevv[x]){
d = min(d, G[prevv[x]][preve[x]].cap);//从t出发沿着最短路返回s找可改进量
sum+=G[prevv[x]][preve[x]].cost;
}
f -= d;
res += d*sum;//h[t]表示最短距离的同时,也代表了这条最短路上的费用之和,乘以流量d即可得到本次增广所需的费用
for (int x = t; x != s; x = prevv[x])
{
Edge&e = G[prevv[x]][preve[x]];
e.cap -= d;//修改残量值
G[x][e.rev].cap += d;
}
}
return res;
}
};