费用流

给定一个网络\(G =(V,E)\),每条边除了有容量限制\(c(u,v)\),还有一个单位限制\(w(u,v)\)

\((u,v)\)的流量为\(f(u,v)\)时,需要花费\(f(u,v)×w(u,v)\)\(w\)也满足斜对称性,即\(w(u,v) = -w(v,u)\)

则该网路中总花费最小的最大流称为最小费用最大流,即在最大化\(\sum_{(s,v)\in E}f(s,v)\)的前提下最小化\(\sum_{(u,v)\in E}f(u,v)×w(u,v)\)

费用

我们定义一条边的费用\(w(u,v)\)表示边\((u,v)\)上单位流量的费用。也就是说,当边\((u,v)\)的流量为\(f(u,v)\)时,需要花费\(f(u,v)×w(u,v)\)的费用。

最小费用最大流

网络流图中,花费最小的最大流被称为最小费用最大流,这也是接下来我们要研究的对象

MCMF算法

在最大流的EK算法求解最大流的基础上,把用bfs求解任意增广路改为用spfa求解单位费用之和最小的增广路即可

相当于把\(w(u,v)\)作为边权,在残余网络上求最短路

struct qxx {
  int nex, t, v, c;
};
qxx e[M];
int h[N], cnt = 1;
void add_path(int f, int t, int v, int c) {
  e[++cnt] = (qxx){h[f], t, v, c}, h[f] = cnt;
}
void add_flow(int f, int t, int v, int c) {
  add_path(f, t, v, c);
  add_path(t, f, 0, -c);
}
int dis[N], pre[N], incf[N];
bool vis[N];
bool spfa() {
  memset(dis, 0x3f, sizeof(dis));
  queue<int>q;
  q.push(s), dis[s] = 0, incf[s] = INF, incf[t] = 0;
  while(q.size()) {
    int u = q.front();
    q.pop();
    vis[u] = 0;
    for (int i = h[u]; i; i = e[i].nex) {
      const int &v = e[i].t, &w = e[i].v, &c = e[i].c;
      if (!w || dis[v] <= dis[u] + c) continue;
      dis[v] = dis[u] + c, incf[v] = min(w, incf[u]), pre[v] = i;
      if (!vis[v]) q.push(v), vis[v] = 1;
    }
  }
  return incf[t];
}
int maxflow, mincost;
void update() {
  maxflow += incf[t];
  for (int u = t; u != s; u = e[pre[u] ^ 1].t) {
    e[pre[u]].v -= incf[t], e[pre[u] ^ 1].v += incf[t];
    mincost += incf[t] * e[pre[u]].c;
  }
}

\(dicnic\)算法

我们可以在\(dinic\)算法的基础上进行改进,把bfs求分层图改为用spfa(由于有负边权,所以不能直接用dijkstra)来求一条单位费用之和最小的路径,也就是把\(w(u,v)\)当做边权然后在参与网络上求最短路,当然在dfs中也要略作修改。遮掩就可以求的网络流图的最小费用最大流了。

如何建反向边?罪域一条边\((u,v,w,c)\)(其中\(w\)\(c\)分别为容量和费用),我们建立正向边\((u,v,w,c)\)和反向边\((v,u,0,-c)\)(其中\(-c\)是使得从反向边经过时退回原来的费用)。

优化:可以使用 Primal-Dual 原始对偶算法将 SPFA 改成 Dijkstra!反正我不会

时间复杂度 :可以证明上界为\(O(nmf)\),其中\(f\)表示流量。

#define B cout << "BreakPoint" << endl;
#define O(x) cout << #x << " " << x << endl;
#define O_(x) cout << #x << " " << x << " ";
#define Msz(x) cout << "Sizeof " << #x << " " << sizeof(x)/1024/1024 << " MB" << endl;
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<set>
#define LL long long
const int inf = 1e9 + 9;
const int N = 2e5 + 5;
using namespace std;
inline int read() {
	int s = 0,w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') {
		if(ch == '-')
			w = -1;
		ch = getchar();
	}
	while(ch >= '0' && ch <= '9') {
		s = s * 10 + ch - '0';
		ch = getchar();
	}
	return s * w;
}
int n,m,tot = 1,head[N],cur[N],to[N],nxt[N],val[N],cost[N],dis[N],ret,INF;
bool vis[N];
void add(int u, int v, int w, int c) {
  to[++tot] = v,nxt[tot] = head[u],head[u] = tot,val[tot] = w,cost[tot] = c;
}
void addedge(int u, int v, int w, int c) { add(u,v,w,c),add(v,u,0,-c); }
bool spfa(int s, int t) {
	memset(dis,127,sizeof(dis));
	INF = dis[0];
	memcpy(cur,head,sizeof(head));
  	queue<int> q;
  	q.push(s),dis[s] = 0,vis[s] = 1;
  	while (!q.empty()) {
    	int u = q.front();
    	q.pop(),vis[u] = 0;
    	for (int i = head[u]; i; i = nxt[i]) {
      		int v = to[i];
      		if(val[i] && dis[v] > dis[u] + cost[i]) {
        		dis[v] = dis[u] + cost[i];
        		if(!vis[v]) q.push(v),vis[v] = 1;
      		}
    	}
  	}
  	return dis[t] != INF;
}
int dfs(int u, int t, int flow) {
	if(u == t) return flow;
  	vis[u] = 1;
  	int ans = 0;
  	for(int i = cur[u];i && ans < flow;i = nxt[i]) {
    	int v = to[i];
   		if(!vis[v] && val[i] && dis[v] == dis[u] + cost[i]) {
      		int x = dfs(v, t, std::min(val[i],flow - ans));
      		if(x) ret += x * cost[i],val[i] -= x,val[i ^ 1] += x,ans += x;
    	}
  	}
  	vis[u] = 0;
  	return ans;
}
int mcmf(int s, int t) {
	int ans = 0;
	while(spfa(s, t)){
    	int x;
    	while((x = dfs(s,t,INF))) ans += x;
  	}
  	return ans;
}
int main() {
	int s, t;
	n = read(),m = read(),s = read(),t = read();
	while (m--) {
    	int u = read(),v = read(),w = read(),c = read();
    	addedge(u,v,w,c);
  	}
	int ans = mcmf(s,t);
  	printf("%d %d\n",ans,ret);
  	return 0;
}

posted @ 2020-02-27 18:56  优秀的渣渣禹  阅读(147)  评论(0编辑  收藏  举报