给定一个网络(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
",ans,ret);
return 0;
}