最大流可以以多种方式到达,求解费用最小的最大流。
(1)Dijkstra算法可以很好地解决无负权边的最短路径问题,而不能解决含有负权边的问题。若当前距离源点最短的点为A,长度为a,了经过其他点B到达A的的路径为b+ab(b为源点到B的距离,ab为边AB的长度),因为a (2)SPFA算法的时间复杂度为O(VE)。主要思想是,设立一个先进先出的队列来保存待优化的节点,当节点u出队时,用其当前的最短距离来更新与其邻接的点v的最短距,若v的最短距离发生了变化,则将v加入队列中。如此一直重复下去,直到队列为空。(每一个点的松弛次数不能大于总的点数,否则出现负权环)SPFA算法能够处理负权边的本质在于,将最短路径的找到等同于队列为空,即一个点可以被松弛多次。
负权环导致问题的原因:
环中某点更新后变小,将其传递给下一个点,由于下一个点本就是由该点更新的,因此下一个点也会更新变小,依此传递下去,若为负权环,则起始点比初始值更小,导致下一轮传播,若非负权环,则不会更新。
(3)在网络流问题中,由于添加了反向边,因此无论增广的顺序如何,最终都能找到最大流。基于此,可以每次去找一条费用最小的增广路径进行流量的增加。
1.以单价为边建立图G
2.使用sfpa求出从源点到汇点的最短路,若找不到,则算法结束;若找到,并找出路径上的最大流量f
3.更新该路径上的残余流量,若等于0,则在图G中删除这条边,转2
注意:反向边的cap为0(使得剩余流量为正数)。且由于反向边是为了反悔用的,因此cost为负数。
#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判断负权环
最小费用最大流