例题POJ 2135
When FJ’s friends visit him on the farm, he likes to show them around. His farm comprises N (1 <= N <= 1000) fields numbered 1..N, the first of which contains his house and the Nth of which contains the big barn. A total M (1 <= M <= 10000) paths that connect the fields in various ways. Each path connects two different fields and has a nonzero length smaller than 35,000.
To show off his farm in the best way, he walks a tour that starts at his house, potentially travels through some fields, and ends at the barn. Later, he returns (potentially through some fields) back to his house again.
He wants his tour to be as short as possible, however he doesn’t want to walk on any given path more than once. Calculate the shortest tour possible. FJ is sure that some tour exists for any given farm.
Input
- Line 1: Two space-separated integers: N and M.
- Lines 2..M+1: Three space-separated integers that define a path: The starting field, the end field, and the path’s length.
Output
A single line containing the length of the shortest tour.
Sample Input
4 5
1 2 1
2 3 1
3 4 1
1 3 2
2 4 2
Sample Output
6
最小费用最大流问题
在最小费用流问题中,用领接阵存储就不合适了。因为可能存在平行边,即有多条从u到v的边但费用却不同,因此还是采用vector<结构体>的形式来储存较好。
录入边的数据时,正向边录入,为了允许反向增广,需要将反向边的容量置为0(若正向边流量增大,则反向边流量减少,变为负值,满足容量大于流量的条件,因此可以在增广过程中被考虑),费用则置为-cost,意味减少费用。
![](/qrcode.jpg)
SPFA(Bellman-Ford)算法
SPFA算法是1994年西安交通大学段凡丁提出,是一种求单源最短路的算法。
建立一个队列,初始时队列里只有起始点,再建立一个表格记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里有的点作为起始点去刷新到所有点的最短路,如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。重复执行直到队列为空。
Tip:若题目没有保证不存在负环,则要判断有无负环:如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)
最小费用流的问题便是:每次使用SPFA算法求增广路径(求费用权值最小的路,而最大流的BFS是随便找一条可行路)。只要初始流是该流量下的最小费用可行流,每次增广后的新流都是新流量下的最小费用流。
分析
这道题目要求走一个来回不重复,那就可以看作从起点到终点找两条不同路径,进一步,可以看作是,流出起点和流入终点的流量为2,但是中间每条路径的最大容量都是1,求这种情况下的最小费用最大流(流量为2)。方便起见,可以建立一个起始节点与起点相连,最大容量为单向的+2;再建立一个与最远节点n相连的节点,最大容量为n节点流向该节点,单向的+2。这样,求起始节点与终节点的最小费用最大流问题套模板即可。
另外需要注意的是,因为路是双向的,所以正反边都要添加要添加要添加
样例实现代码
/*
ZhangBinjie@Penguin
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
#define maxn 1000+5
#define INF 0x7f7f7f7f
using namespace std;
struct edge {
int from, to, cap, flow, cost;
edge(int u, int v, int c, int f, int w) :from(u), to(v), cap(c), flow(f), cost(w) {}
};
int n, m;
vector<edge>edges;
vector<int>G[maxn]; //G[i]储存从节点i射出的弧线的编号
int pre[maxn]; //pre[i]储存射入节点i的弧线的编号
int d[maxn]; //SPFA
int a[maxn]; //a[i]储存到节点的最大增广量
int inq[maxn];
void init() {
for (int i = 0; i <= n + 1; ++i)
G[i].clear();
edges.clear();
}
void addedge(int u, int v, int c, int w) {
edges.push_back(edge(u, v, c, 0, w));
edges.push_back(edge(v, u, 0, 0, -w));
int s = edges.size();
G[u].push_back(s - 2);
G[v].push_back(s - 1);
}
bool SPFA(int s, int t, int& flow, long long& cost) {
memset(d, INF, sizeof(d));
memset(inq, 0, sizeof(inq));
pre[0] = 0;
d[0] = 0;
a[0] = INF;
inq[0] = 1;
queue<int>q;
q.push(0);
int u;
while (!q.empty()) {
u = q.front();
q.pop();
inq[u] = 0;
for (int i = 0; i<G[u].size(); ++i) {
edge& e = edges[G[u][i]];
if (e.cap>e.flow&&d[e.to]>d[u] + e.cost) {
a[e.to] = min(a[u], e.cap - e.flow);
d[e.to] = d[u] + e.cost;
pre[e.to] = G[u][i];
if (!inq[e.to]) {
q.push(e.to);
inq[e.to] = 1;
}
}
}
}
if (d[n + 1] == INF)
return false;
flow += a[n + 1];
cost += (long long)a[n + 1] * (long long)d[n + 1];
for (u = n + 1; u != s; u = edges[pre[u]].from) {
edges[pre[u]].flow += a[n + 1];
edges[pre[u] ^ 1].flow -= a[n + 1];
}
return true;
}
int MincostMaxflow(int s, int t, long long& cost) {
int flow = 0;
cost = 0;
while (SPFA(s, t, flow, cost));
return flow;
}
int main() {
int a, b, w;
long long cost;
while (scanf("%d%d",&n,&m)==2) {
for (int i = 0; i < m; ++i) {
//cin >> a >> b >> w;
scanf("%d%d%d",&a,&b,&w);
addedge(a, b, 1, w);
addedge(b, a, 1, w);
//因为路是双向的,所以正反边都要添加要添加要添加
}
addedge(0, 1, 2, 0);
addedge(n, n + 1, 2, 0);
MincostMaxflow(0, n + 1, cost);
cout << cost << endl;
}
return 0;
}
本题的其余可改进方法
当流量已经为2时,就可以直接结束后续所有的SPFA了。