zoukankan      html  css  js  c++  java
  • [算法] 最小费用最大流

    思想

    主要是把无权最大流中找增广路时,每次都增广以费用为计算的最短路,时间复杂度较高,但通常可以计算出 \(n\leq 1e4\) 的数据。

    常用定理:每次增广时的最短路长度严格递增,且不存在负环。

    Code

    几种常见的最小费用最大流的写法:

    EK+SPFA

    #include <deque>
    #include <cstdio>
    using namespace std;
    const int MAXN = 5e3 + 5;
    const int MAXM = 5e4 + 5;
    const int INF = 0x7f7f7f7f;
    struct Edge { int To, Cap, Cost, Next; };
    Edge edge[MAXM << 1];
    int head[MAXN], tot = 1;
    void Addedge(int u, int v, int w, int c) {
    	edge[++tot].Next = head[u], edge[tot].To = v, edge[tot].Cap = w, edge[tot].Cost = c, head[u] = tot;
    	edge[++tot].Next = head[v], edge[tot].To = u, edge[tot].Cap = 0, edge[tot].Cost = -c, head[v] = tot;
    }
    deque<int> dq;
    int dis[MAXN], nxt[MAXN], lim[MAXN];
    bool inque[MAXN];
    int n, m, s, t, mcmf;
    int Min(int x, int y) {
    	return x < y ? x : y;
    }
    bool SPFA() {
    	for (int i = 1; i <= n; i++) dis[i] = INF, lim[i] = INF;
    	dis[t] = 0, inque[t] = 1, dq.push_back(t);
    	while (!dq.empty()) {
    		int u = dq.front(); dq.pop_front(), inque[u] = 0;
    		for (int i = head[u]; i; i = edge[i].Next) {
    			int v = edge[i].To;
    			if (dis[v] > dis[u] - edge[i].Cost && edge[i ^ 1].Cap) {
    				dis[v] = dis[u] - edge[i].Cost, nxt[v] = i ^ 1, lim[v] = min(lim[u], edge[i ^ 1].Cap);
    				if (!inque[v]) {
    					if (!dq.empty() && dis[v] < dis[dq.front()]) dq.push_front(v);
    					else dq.push_back(v);
    				}
    				inque[v] = 1;
    			}
    		}
    	}
    	return dis[s] != INF;
    }
    int Update() {
    	int now = s;
    	while (now != t) {
    		int i = nxt[now];
    		edge[i].Cap -= lim[s];
    		edge[i ^ 1].Cap += lim[s];
    		mcmf += lim[s] * edge[i].Cost;
    		now = edge[i].To;
    	}
    	return lim[s];
    }
    int EK() {
    	int res = 0;
    	while (SPFA()) res += Update();
    	return res;
    }
    int main() {
    	scanf("%d %d %d %d", &n, &m, &s, &t);
    	for (int i = 1, u, v, w, c; i <= m; i++) {
    		scanf("%d %d %d %d", &u, &v, &w, &c);
    		Addedge(u, v, w, c);
    	}
    	printf("%d ", EK());
    	printf("%d", mcmf);
    	return 0;
    }
    

    Dinic+SPFA

    只是改为了多路增广,但由于最短路的要求过于苛刻,效率与 EK 算法差距不是特别大。

    #include <deque>
    #include <cstdio>
    using namespace std;
    const int MAXN = 5e3 + 5;
    const int MAXM = 5e4 + 5;
    const int INF = 0x7f7f7f7f;
    struct Edge { int To, Cap, Cost, Next; };
    Edge edge[MAXM << 1];
    int head[MAXN], tot = 1;
    void Addedge(int u, int v, int w, int c) {
    	edge[++tot].Next = head[u], edge[tot].To = v, edge[tot].Cap = w, edge[tot].Cost = c, head[u] = tot;
    	edge[++tot].Next = head[v], edge[tot].To = u, edge[tot].Cap = 0, edge[tot].Cost = -c, head[v] = tot;
    }
    deque<int> dq;
    int dis[MAXN];
    bool inque[MAXN], vis[MAXN];
    int n, m, s, t, mcmf;
    int Min(int x, int y) {
    	return x < y ? x : y;
    }
    bool SPFA() {
    	for (int i = 1; i <= n; i++) dis[i] = INF;
    	dis[t] = 0, inque[t] = 1, dq.push_back(t);
    	while (!dq.empty()) {
    		int u = dq.front(); dq.pop_front(), inque[u] = 0;
    		for (int i = head[u]; i; i = edge[i].Next) {
    			int v = edge[i].To;
    			if (dis[v] > dis[u] - edge[i].Cost && edge[i ^ 1].Cap) {
    				dis[v] = dis[u] - edge[i].Cost;
    				if (!inque[v]) {
    					if (!dq.empty() && dis[v] < dis[dq.front()]) dq.push_front(v);
    					else dq.push_back(v);
    				}
    				inque[v] = 1;
    			}
    		}
    	}
    	return dis[s] != INF;
    }
    int dfs(int u, int flow) {
    	if (!flow || u == t) return flow;
    	int rest = flow;
    	vis[u] = 1;
    	for (int i = head[u]; i && rest; i = edge[i].Next) {
    		int v = edge[i].To;
    		if (!vis[v] && dis[v] == dis[u] - edge[i].Cost && edge[i].Cap) {
    			int del = dfs(v, Min(edge[i].Cap, rest));
    			edge[i].Cap -= del, edge[i ^ 1].Cap += del, rest -= del;
    			mcmf += del * edge[i].Cost;
    		}
    	}
    	vis[u] = 0;
    	return flow - rest;
    }
    int Dinic() {
    	int res = 0, flow;
    	while (SPFA()) while ((flow = dfs(s, INF))) res += flow;
    	return res;
    }
    int main() {
    	scanf("%d %d %d %d", &n, &m, &s, &t);
    	for (int i = 1, u, v, w, c; i <= m; i++) {
    		scanf("%d %d %d %d", &u, &v, &w, &c);
    		Addedge(u, v, w, c);
    	}
    	printf("%d ", Dinic());
    	printf("%d", mcmf);
    	return 0;
    }
    

    EK+Dijkstra

    将 SPFA 改为带势 Dijkstra,将 \(w[u][v]\) 转化为 \(h[u]-h[v]+w[u][v]\),那么 \(s\to t\) 的中间节点的势函数都被消掉了。

    但需要保证 \(h[u]-h[v]+w[u][v]\geq0\) ,对于每次求最短路时 \(dis\) 不为极大值的点,势函数加上最短路。即 \(h[u]+=dis[u](dis[u]\neq \infty)\)

    证明该构造方案合法:

    \(h[u]-h[v]+w[u][v]\geq0\) 移项变为 \(h[u]+w[u][v]\geq h[v]\)

    • \(w[u][v]>0\) 时,显然成立。
    • \(w[u][v]<0\) 。若当前反边是新增的,那么存在一次正边被增广。即 \(dis'[v]-w[u][v]=dis'[u]\to dis'[v]=dis'[u]+w[u][v]\),由于正边是在情况 \(1\) 中成立的,那么此时的 \(dis'[v]<dis'[u]\),而累加到 \(h\) 中刚好将 \(w[u][v]\) 抵消了,则也成立,简单来说,就是正边的增广将反边的增广相抵消了。否则,\(h=\Delta dis+k\) ,而当前边的方向并没有改变,则 \(\Delta dis\) 并不会发生改变,其中 \(k\) 是由最短路中的其他边所构成的,\(u,v\)\(k\) 相等,并不会影响正确性。

    若图中存在负边,那么先用 SPFA 求出第一次的势函数,再用 Dijkstra 修改势函数。

    #include <deque>
    #include <cstdio>
    using namespace std;
    const int MAXN = 5e3 + 5;
    const int MAXM = 5e4 + 5;
    const int INF = 0x7f7f7f7f;
    struct Edge { int To, Cap, Cost, Next; };
    Edge edge[MAXM << 1];
    int head[MAXN], tot = 1;
    void Addedge(int u, int v, int w, int c) {
    	edge[++tot].Next = head[u], edge[tot].To = v, edge[tot].Cap = w, edge[tot].Cost = c, head[u] = tot;
    	edge[++tot].Next = head[v], edge[tot].To = u, edge[tot].Cap = 0, edge[tot].Cost = -c, head[v] = tot;
    }
    deque<int> dq;
    int dis[MAXN], nxt[MAXN], lim[MAXN];
    bool inque[MAXN];
    int n, m, s, t, mcmf;
    int Min(int x, int y) {
    	return x < y ? x : y;
    }
    bool SPFA() {
    	for (int i = 1; i <= n; i++) dis[i] = INF, lim[i] = INF;
    	dis[t] = 0, inque[t] = 1, dq.push_back(t);
    	while (!dq.empty()) {
    		int u = dq.front(); dq.pop_front(), inque[u] = 0;
    		for (int i = head[u]; i; i = edge[i].Next) {
    			int v = edge[i].To;
    			if (dis[v] > dis[u] - edge[i].Cost && edge[i ^ 1].Cap) {
    				dis[v] = dis[u] - edge[i].Cost, nxt[v] = i ^ 1, lim[v] = min(lim[u], edge[i ^ 1].Cap);
    				if (!inque[v]) {
    					if (!dq.empty() && dis[v] < dis[dq.front()]) dq.push_front(v);
    					else dq.push_back(v);
    				}
    				inque[v] = 1;
    			}
    		}
    	}
    	return dis[s] != INF;
    }
    int Update() {
    	int now = s;
    	while (now != t) {
    		int i = nxt[now];
    		edge[i].Cap -= lim[s];
    		edge[i ^ 1].Cap += lim[s];
    		mcmf += lim[s] * edge[i].Cost;
    		now = edge[i].To;
    	}
    	return lim[s];
    }
    int EK() {
    	int res = 0;
    	while (SPFA()) res += Update();
    	return res;
    }
    int main() {
    	scanf("%d %d %d %d", &n, &m, &s, &t);
    	for (int i = 1, u, v, w, c; i <= m; i++) {
    		scanf("%d %d %d %d", &u, &v, &w, &c);
    		Addedge(u, v, w, c);
    	}
    	printf("%d ", EK());
    	printf("%d", mcmf);
    	return 0;
    }
    

    例:[SDOI2017]新生舞会

    题目链接(洛谷)

    题意

    \(n\) 个点,第 \(i\) 个点与第 \(j\) 个点匹配会产生 \(a_{i,j}\)\(b_{i,j}\),每个点只能匹配一次。

    \(\frac{\sum a}{\sum b}\) 的最大值。

    思路

    二分图带权匹配,再用 \(0/1\) 分数规划的套路,使用费用流求解即可

    Code

    #include <map>
    #include <queue>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    const int MAXN = 2e2 + 5;
    const int MAXM = 2e4 + 5;
    const int INF = 0x3f3f3f3f;
    #define Eps 1e-8
    struct Edge { int To, Cap, Next; long double Cost; };
    Edge edge[MAXM << 1];
    int head[MAXN], tot = 1;
    void Addedge(int u, int v, int w, long double c) {
    	edge[++tot].Next = head[u], edge[tot].To = v, edge[tot].Cap = w, edge[tot].Cost = c, head[u] = tot;
    	edge[++tot].Next = head[v], edge[tot].To = u, edge[tot].Cap = 0, edge[tot].Cost = -c, head[v] = tot;
    }
    priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > pq;
    long double dis[MAXN], h[MAXN], mcmf;
    int dep[MAXN], pre[MAXN], lim[MAXN];
    bool vis[MAXN];
    int a[105][105], b[105][105];
    int n, m, s, t;
    int Min(int x, int y) {
    	return x < y ? x : y;
    }
    bool Dijkstra() {
    	for (int i = s; i <= t; i++) vis[i] = 0, pre[i] = 0, lim[i] = dis[i] = INF;
    	dis[s] = 0, pq.push(make_pair(0, s));
    	while (!pq.empty()) {
    		int u = pq.top().second; pq.pop();
    		if (vis[u]) continue; vis[u] = 1;
    		for (int i = head[u]; i; i = edge[i].Next) {
    			int v = edge[i].To;
    			if (dis[v] > dis[u] + edge[i].Cost + h[u] - h[v] && edge[i].Cap) {
    				pre[v] = i, lim[v] = min(lim[u], edge[i].Cap);
    				dis[v] = dis[u] + edge[i].Cost + h[u] - h[v];
    				pq.push(make_pair(dis[v], v));
    			}
    		}
    	}
    	for (int i = s; i <= t; i++) if (dis[i] < INF) h[i] += dis[i];
    	return dis[t] != INF;
    }
    int Update() {
    	int now = t;
    	while (now != s) {
    		int i = pre[now];
    		edge[i].Cap -= lim[t];
    		edge[i ^ 1].Cap += lim[t];
    		mcmf += lim[t] * edge[i].Cost;
    		now = edge[i ^ 1].To;
    	}
    	return lim[t];
    }
    int EK() {
    	int res = 0;
    	while (Dijkstra()) res += Update();
    	return res;
    }
    bool Check(long double mid) {
    	for (int i = s; i <= t; i++) head[i] = 0, h[i] = 0; tot = 1; mcmf = 0;
    	t = 2 * n + 1;
    	for (int i = 1; i <= n; i++) Addedge(s, i, 1, 0);
    	for (int i = 1; i <= n; i++) Addedge(i + n, t, 1, 0);
    	for (int i = 1; i <= n; i++) {
    		for (int j = 1; j <= n; j++) {
    			Addedge(i, j + n, 1, mid * b[i][j] - a[i][j]);
    		}
    	}
    	EK();
    	return mcmf <= 0;
    }
    int main() {
    	scanf("%d", &n);
    	for (int i = 1; i <= n; i++)
    		for (int j = 1; j <= n; j++)
    			scanf("%d", &a[i][j]);
    	for (int i = 1; i <= n; i++)
    		for (int j = 1; j <= n; j++)
    			scanf("%d", &b[i][j]);
    	long double l = 0, r = 1e4;	
    	while (r - l >= Eps) {
    		long double mid = (l + r) / 2;
    		if (Check(mid)) l = mid;
    		else r = mid;
    	}
    	printf("%.6Lf", l);
    	return 0;
    }
    
  • 相关阅读:
    Linux内核中的红黑树
    研究UEVENT相关东西,看到2篇优秀的博文,转载与此
    Nor Nand OneNand
    Linux设备模型(总线、设备、驱动程序和类)
    linux驱动的入口函数module_init的加载和释放(转)
    hwclock(Linux)
    Linux块设备驱动
    pdflush内核线程池及其中隐含的竞争
    Nand Flash与Nor Flash
    linux内核I2C体系结构(注意结构体原型)
  • 原文地址:https://www.cnblogs.com/C202202chenkelin/p/15773161.html
Copyright © 2011-2022 走看看