网络中最小费用最大流

原博客地址

前言

如果还没有学过最大流,戳这里

本文用到了优化的 S P F A SPFA SPFA简易的代码

一组简单的小样例:

输入

4 5 4 3
4 2 30 2
4 3 20 3
2 3 20 1
2 1 30 9
1 3 40 5

输出

50 280

思路

对于满足最大流合法的流函数可能有很多,但加上边的单位花费,则方案会减少很多。

实质上是在 E d m o n d − K a r p Edmond-Karp EdmondKarp 算法上进行改进。

E d m o n d − K a r p Edmond-Karp EdmondKarp 算法是对于残量网络上仅找到 1 1 1 条增广路进行增广。

而求最小费用最大流只需要在寻找增广路的过程中寻找花费最小的增广路。

建图

需要包含四个信息:终点节点编号,边的容量,边的花费,相反的边的编号。

struct Node {
	int to, val, cost, rev;
	Node() {}
	Node(int T, int L, int C, int R) {
		to = T;//终点节点编号
		val = L;//边的容量
		cost = C;//边的花费
		rev = R;//相反的边的编号
	}
};

确定增广路径

S P F A SPFA SPFA 实现,若不存在增广路径,则停止增广。

增广

S P F A SPFA SPFA 的过程中记录这条最短路路径编号,并记录这条路上的最小剩余容量。

用一个 w h i l e while while 循环寻找这条路径,并且直接记录这条路径的花费即可。

C++代码

#include <map>
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
#define INF 0x3f3f3f3f
#define Min(a, b) ((a) < (b) ? (a) : (b))
void Quick_Read(int &N) {
	N = 0;
	int op = 1;
	char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-')
			op = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9') {
		N = (N << 1) + (N << 3) + (c ^ 48);
		c = getchar();
	}
	N *= op;
}
const int MAXN = 5e3 + 5;
struct Node {
	int to, val, cost, rev;
	Node() {}
	Node(int T, int L, int C, int R) {
		to = T;
		val = L;
		cost = C;
		rev = R;
	}
};
vector<Node> v[MAXN];
pair<int, int> pre[MAXN];
deque<int> q;
int dis[MAXN], maf[MAXN];
bool inque[MAXN], vis[MAXN];
int n, m, s, t;
int ans;
bool SPFA() {
	for(int i = 1; i <= n; i++)
		inque[i] = false, dis[i] = INF, maf[i] = -INF;
	int iqn = 1, fis = 0;//优化
	maf[s] = INF;
	dis[s] = 0;
	inque[s] = true;
	q.push_back(s);
	while(!q.empty()) {
		int now = q.front(); q.pop_front();
		inque[now] = false;
		fis -= dis[now];
		iqn--;
		int SIZ= v[now].size();
		for(int i = 0; i < SIZ; i++) {
			int next = v[now][i].to;
			if(dis[next] > dis[now] + v[now][i].cost && v[now][i].val) {
				maf[next] = Min(maf[now], v[now][i].val);//记录最小剩余容量
				pre[next].first = now;//记录路径	
				pre[next].second = i;
				dis[next] = dis[now] + v[now][i].cost;
				if(!inque[next]) {
					inque[next] = true;
					if(q.empty() || dis[next] > dis[q.front()] || dis[next] * iqn <= fis)
						q.push_back(next);
					else
						q.push_front(next);
					fis += dis[next] + v[now][i].cost;
					iqn++;
				}
			}
		}
	}
	return dis[t] != INF;//是否存在增广路
}
int Update() {
	int now = t;
	while(now != s) {
		int next = pre[now].first;
		int i = pre[now].second;
		v[next][i].val -= maf[t];//减去最小该路径的最小剩余容量,更新剩余流量
		v[now][v[next][i].rev].val += maf[t];//流量守恒
		ans += v[next][i].cost * maf[t];//记录价值
		now = next;
	}
	return maf[t];
}
int Edmond_Karp() {//求解最小费用最大流
	int res = 0;
	while(SPFA()) {
		do {
			res += Update();
		} while(vis[t]);
	}
	return res;
}
void Read() {
	int A, B, C, D;
	Quick_Read(n);
	Quick_Read(m);
	Quick_Read(s);
	Quick_Read(t);
	for(int i = 1; i <= m; i++) {
		Quick_Read(A);
		Quick_Read(B);
		Quick_Read(C);
		Quick_Read(D);
		int idA = v[A].size();
		int idB = v[B].size();
		v[A].push_back(Node(B, C, D, idB));
		v[B].push_back(Node(A, 0, -D, idA));//记得双向存边
	}
}
int main() {
	Read();
	printf("%d ", Edmond_Karp());
	printf("%d", ans);
	return 0;
}

看看自己做对没有吖

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值