最小费用最大流

最小费用最大流

1.解决的问题

最大流可以以多种方式到达,求解费用最小的最大流。

2.预备知识

(1)Dijkstra算法可以很好地解决无负权边的最短路径问题,而不能解决含有负权边的问题。若当前距离源点最短的点为A,长度为a,了经过其他点B到达A的的路径为b+ab(b为源点到B的距离,ab为边AB的长度),因为a (2)SPFA算法的时间复杂度为O(VE)。主要思想是,设立一个先进先出的队列来保存待优化的节点,当节点u出队时,用其当前的最短距离来更新与其邻接的点v的最短距,若v的最短距离发生了变化,则将v加入队列中。如此一直重复下去,直到队列为空。(每一个点的松弛次数不能大于总的点数,否则出现负权环)SPFA算法能够处理负权边的本质在于,将最短路径的找到等同于队列为空,即一个点可以被松弛多次。

负权环导致问题的原因:
环中某点更新后变小,将其传递给下一个点,由于下一个点本就是由该点更新的,因此下一个点也会更新变小,依此传递下去,若为负权环,则起始点比初始值更小,导致下一轮传播,若非负权环,则不会更新。

(3)在网络流问题中,由于添加了反向边,因此无论增广的顺序如何,最终都能找到最大流。基于此,可以每次去找一条费用最小的增广路径进行流量的增加。

3.解决思路

1.以单价为边建立图G
2.使用sfpa求出从源点到汇点的最短路,若找不到,则算法结束;若找到,并找出路径上的最大流量f
3.更新该路径上的残余流量,若等于0,则在图G中删除这条边,转2

注意:反向边的cap为0(使得剩余流量为正数)。且由于反向边是为了反悔用的,因此cost为负数。

4.例题 poj2135

#include
#include
#include
using namespace std;
#define NMAX 1100
#define INF 0x3f3f3f3f
struct edge {
	int from, to, cap, flow, cost;
};
vector<edge> E;
vector<int> G[NMAX];//记录的是起点为i的所有的边
//题目当中的变量
int N, M; //N代表的是不包括源点和汇点的总共点数
//Mincost需要使用的变量
int inq[NMAX] = { 0 };//代表是否在队列中
int dis[NMAX];//代表的是源点到点的距离
int cflow[NMAX]; //代表当前路径上的最小流量
int pre[NMAX] = { 0 };//代表导向该点的上一条弧
void AddEdge(int from ,int to, int cap, int cost) {
	struct edge ce;
	ce.from = from, ce.to = to, ce.cap = cap, ce.flow = 0, ce.cost = cost;
	E.push_back(ce);
	ce.from = to, ce.to = from, ce.cap = 0, ce.flow = 0, ce.cost = -cost;
	E.push_back(ce);//反向边的容量为0,价格为负数
	int m = E.size();
	G[from].push_back(m - 2);
	G[to].push_back(m - 1);
}

int SPFA(int s, int d, int& cost, int& flow) {

	for (int i = 1; i <= N + 2; i++) dis[i] = INF, cflow[i] = INF, inq[i] = 0;
	queue<int> Q;//存放点
	Q.push(s);
	inq[s] = 1;//代表源点已经放入队列中
	dis[s] = 0;//源点的距离一定要初始化为0
	while (!Q.empty()) {
		int cs = Q.front();
		Q.pop();
		inq[cs] = 0;//cs出队列
		for (int i = 0; i < G[cs].size(); i++) {
			int num = G[cs][i];
			int cd = E[num].to; 
			//判断条件先看这条边是否还有流量
			if ((E[num].cap - E[num].flow) > 0) {
				if (dis[cd] > E[num].cost + dis[cs] ) {
					//更新最短距离
					dis[cd] = E[num].cost + dis[cs];
					//更新路径上的最小流量
					cflow[cd] = min(cflow[cs], E[num].cap - E[num].flow);
					//更新链接关系
					pre[cd] = num;
					//如果没有在队列中,则将其加入队列以方便对于其他元素进行更新
					if (!inq[cd]) {
						Q.push(cd);
						inq[cd] = 1;
					}
				}
			}
		}
	}
	if (dis[d] == INF) return 0;
	//更新流量和成本
	flow += cflow[d];
	cost += dis[d] * cflow[d];

	//利用pre更新图,注意首先应该从点出发
	//需要注意的是,这里是双向图
	int cur = d;
	while (cur != s) {
		int num = pre[cur];
		E[num].flow += cflow[d];
		E[num ^ 1].flow -= cflow[d]; //num ^ 1代表的是相反的那条边的编号
		cur = E[num].from;
	}
	return 1;
}
int Mincost(int s, int d) {
	int cost = 0, flow = 0;
	while (SPFA(s, d, cost, flow));
	return cost;
}

int main() {
	cin >> N >> M;
	for (int i = 0; i < M; i++) {
		int s, d, l;
		cin >> s >> d >> l;
		AddEdge(s, d, 1, l);
		AddEdge(d, s, 1, l);//无向图注意反向边的添加
	}
	AddEdge(N + 1, 1, 2, 0);
	AddEdge(N, N + 2, 2, 0);
	cout << Mincost(N + 1, N + 2);
}

参考链接:
SPFA判断负权环
最小费用最大流

你可能感兴趣的:(算法,c++,数据结构)