一、最小费用最大流的模型
在保证流量最大的前提下,所需的费用最小,这就是最小费用最大流问题.
带有费用的网络流图: G=(V,E,C,W)
V:顶点; E:弧;C:弧的容量;W:单位流量费用。
任意的弧<i,j>对应非负的容量c[i,j]和单位流量费用w[i,j]。满足:
① 流量f是G的最大流。
② 在f是G的最大流的前提下,流的费用最小。
F是G的最大流的集合(最大流不止一个):
在最大流中寻找一个费用最小的流 f.
二、最小费用最大流的算法
基本思路:
把弧<i,j>的单位费用w[i,j]看作弧<i,j>的路径长度,每次找从源点s到汇点t长度最短(费用最小)的可增广路径进行增广。
1. 最小费用可增广路
2. 路径s到t的长度即单位流量的费用。
ps:是网络流EK算法的改进,在求增广路径的时候,把bfs改为带权的spfa,每次求权值最小的增广路。
ps:要注意一点,逆边cost[i][j] = -cost[j][i],不能忘了加上去。
1 自己的模板:邻接矩阵。
2
3 #include<iostream>
4 using namespace std;
5
6
7
8 int n, ans;
9 int cap[Max][Max], pre[Max];
10 int cost[Max][Max], dis[Max];
11 int que[Max];
12 bool vis[Max];
13
14
15
16 bool spfa(){ // 源点为0,汇点为n。
17 int i, head = 0, tail = 1;
18 for(i = 0; i <= n; i ++){
19 dis[i] = inf;
20 vis[i] = false;
21 }
22 dis[0] = 0;// dis 表示 最小 花费
23 que[0] = 0;
24
25 vis[u] = true;
26
27 while(tail != head){ // 循环队列。
28 int u = que[head];
29
30 for(i = 0; i <= n; i ++)
31 if(cap[u][i] && dis[i] > dis[u] + cost[u][i]){ // 存在路径,且权值变小。
32 dis[i] = dis[u] + cost[u][i];
33 pre[i] = u;
34 if(!vis[i]){
35 vis[i] = true;
36 que[tail ++] = i;
37 if(tail == Max) tail = 0;
38 }
39 }
40 vis[u] = false;
41 head ++;
42 if(head == Max) head = 0;
43 }
44 if(dis[n] == inf) return false;
45 return true;
46 }
47
48
49
50 void end(){
51 int i, sum = inf;
52 for(i = n; i != 0; i = pre[i])
53 sum = min(sum, cap[pre[i]][i]);
54 for(i = n; i != 0; i = pre[i]){
55 cap[pre[i]][i] -= sum;
56 cap[i][pre[i]] += sum;
57 ans += cost[pre[i]][i] * sum; // cost[][]记录的为单位流量费用,必须得乘以流量。
58 }
59 }
60
61
62
63 int main(){
64 ....
65 ans = 0;
66 while(spfa()) end();
67 ....
68 return 0;
69 }
2
3 #include<iostream>
4 using namespace std;
5
6
7
8 int n, ans;
9 int cap[Max][Max], pre[Max];
10 int cost[Max][Max], dis[Max];
11 int que[Max];
12 bool vis[Max];
13
14
15
16 bool spfa(){ // 源点为0,汇点为n。
17 int i, head = 0, tail = 1;
18 for(i = 0; i <= n; i ++){
19 dis[i] = inf;
20 vis[i] = false;
21 }
22 dis[0] = 0;// dis 表示 最小 花费
23 que[0] = 0;
24
25 vis[u] = true;
26
27 while(tail != head){ // 循环队列。
28 int u = que[head];
29
30 for(i = 0; i <= n; i ++)
31 if(cap[u][i] && dis[i] > dis[u] + cost[u][i]){ // 存在路径,且权值变小。
32 dis[i] = dis[u] + cost[u][i];
33 pre[i] = u;
34 if(!vis[i]){
35 vis[i] = true;
36 que[tail ++] = i;
37 if(tail == Max) tail = 0;
38 }
39 }
40 vis[u] = false;
41 head ++;
42 if(head == Max) head = 0;
43 }
44 if(dis[n] == inf) return false;
45 return true;
46 }
47
48
49
50 void end(){
51 int i, sum = inf;
52 for(i = n; i != 0; i = pre[i])
53 sum = min(sum, cap[pre[i]][i]);
54 for(i = n; i != 0; i = pre[i]){
55 cap[pre[i]][i] -= sum;
56 cap[i][pre[i]] += sum;
57 ans += cost[pre[i]][i] * sum; // cost[][]记录的为单位流量费用,必须得乘以流量。
58 }
59 }
60
61
62
63 int main(){
64 ....
65 ans = 0;
66 while(spfa()) end();
67 ....
68 return 0;
69 }
1 自己的模板:邻接表。
2
3 #include<iostream>
4 using namespace std;
5
6
7
8 struct{
9 int v, cap, cost, next, re; // re记录逆边的下标。
10 }edge[eMax];
11 int n, m, ans;
12 int k, edgeHead[nMax];
13 int que[nMax], pre[nMax], dis[nMax];
14 bool vis[nMax];
15
16
17
18 void addEdge(int u, int v, int ca, int co){
19 edge[k].v = v;
20 edge[k].cap = ca;
21 edge[k].cost = co;
22 edge[k].next = edgeHead[u];
23 edge[k].re = k + 1;
24 edgeHead[u] = k ++;
25 edge[k].v = u;
26 edge[k].cap = 0;
27 edge[k].cost = -co;
28 edge[k].next = edgeHead[v];
29 edge[k].re = k - 1;
30 edgeHead[v] = k ++;
31 }
32
33
34
35 bool spfa(){ // 源点为0,汇点为n。
36 int i, head = 0, tail = 1;
37 for(i = 0; i <= n; i ++){
38 dis[i] = inf;
39 vis[i] = false;
40 }
41 dis[0] = 0;
42 que[0] = 0;
43
44 vis[u] = true;
45 while(tail > head){ // 这里最好用队列,有广搜的意思,堆栈像深搜。
46 int u = que[head ++];
47
48 for(i = edgeHead[u]; i != 0; i = edge[i].next){
49 int v = edge[i].v;
50 if(edge[i].cap && dis[v] > dis[u] + edge[i].cost){
51 dis[v] = dis[u] + edge[i].cost;
52 pre[v] = i;
53 if(!vis[v]){
54 vis[v] = true;
55 que[tail ++] = v;
56 }
57 }
58 }
59 vis[u] = false;
60 }
61 if(dis[n] == inf) return false;
62 return true;
63 }
64
65
66
67 void end(){
68 int u, p, sum = inf;
69 for(u = n; u != 0; u = edge[edge[p].re].v){
70 p = pre[u];
71 sum = min(sum, edge[p].cap);
72 }
73 for(u = n; u != 0; u = edge[edge[p].re].v){
74 p = pre[u];
75 edge[p].cap -= sum;
76 edge[edge[p].re].cap += sum;
77 ans += sum * edge[p].cost; // cost记录的为单位流量费用,必须得乘以流量。
78 }
79 }
80
81
82
83 int main(){
84
85 ...
86
87 ans = 0;
88 while(spfa()) end();
89 ...
90
91 return 0;
92 }
93
94
2
3 #include<iostream>
4 using namespace std;
5
6
7
8 struct{
9 int v, cap, cost, next, re; // re记录逆边的下标。
10 }edge[eMax];
11 int n, m, ans;
12 int k, edgeHead[nMax];
13 int que[nMax], pre[nMax], dis[nMax];
14 bool vis[nMax];
15
16
17
18 void addEdge(int u, int v, int ca, int co){
19 edge[k].v = v;
20 edge[k].cap = ca;
21 edge[k].cost = co;
22 edge[k].next = edgeHead[u];
23 edge[k].re = k + 1;
24 edgeHead[u] = k ++;
25 edge[k].v = u;
26 edge[k].cap = 0;
27 edge[k].cost = -co;
28 edge[k].next = edgeHead[v];
29 edge[k].re = k - 1;
30 edgeHead[v] = k ++;
31 }
32
33
34
35 bool spfa(){ // 源点为0,汇点为n。
36 int i, head = 0, tail = 1;
37 for(i = 0; i <= n; i ++){
38 dis[i] = inf;
39 vis[i] = false;
40 }
41 dis[0] = 0;
42 que[0] = 0;
43
44 vis[u] = true;
45 while(tail > head){ // 这里最好用队列,有广搜的意思,堆栈像深搜。
46 int u = que[head ++];
47
48 for(i = edgeHead[u]; i != 0; i = edge[i].next){
49 int v = edge[i].v;
50 if(edge[i].cap && dis[v] > dis[u] + edge[i].cost){
51 dis[v] = dis[u] + edge[i].cost;
52 pre[v] = i;
53 if(!vis[v]){
54 vis[v] = true;
55 que[tail ++] = v;
56 }
57 }
58 }
59 vis[u] = false;
60 }
61 if(dis[n] == inf) return false;
62 return true;
63 }
64
65
66
67 void end(){
68 int u, p, sum = inf;
69 for(u = n; u != 0; u = edge[edge[p].re].v){
70 p = pre[u];
71 sum = min(sum, edge[p].cap);
72 }
73 for(u = n; u != 0; u = edge[edge[p].re].v){
74 p = pre[u];
75 edge[p].cap -= sum;
76 edge[edge[p].re].cap += sum;
77 ans += sum * edge[p].cost; // cost记录的为单位流量费用,必须得乘以流量。
78 }
79 }
80
81
82
83 int main(){
84
85 ...
86
87 ans = 0;
88 while(spfa()) end();
89 ...
90
91 return 0;
92 }
93
94