最小费用最大流及习题(poj)

该算法讲解来源:https://www.cnblogs.com/gtarcoder/p/4890739.html

最小费用最大流

    通过EK,Dinic,ISAP算法可以得到网络流图中的最大流,一个网络流图中最大流的流量max_flow是唯一的,但是达到最大流量max_flow时每条边上的流量分配f是不唯一的。 
    如果给网络流图中的每条边都设置一个费用cost,表示单位流量流经该边时会导致花费cost。那么在这些流量均为max_flow的流量分配f中,存在一个流量总花费最小的最大流方案。 
即 min{sum(cost(i, j)*f(i,j) | (i, j)属于方案f中的边, f(i,j)为 边(i,j)上的流量, f为某一个最大流方案}。此即为最小费用最大流

算法思想

    采用贪心的思想,每次找到一条从源点到达汇点的路径,增加流量,且该条路径满足使得增加的流量的花费最小,直到无法找到一条从源点到达汇点的路径,算法结束。 
    由于最大流量有限,每执行一次循环流量都会增加,因此该算法肯定会结束,且同时流量也必定会达到网络的最大流量;同时由于每次都是增加的最小的花费,即当前的最小花费是所有到达当前流量flow时的花费最小值,因此最后的总花费最小。

求解步骤

(1)找到一条从源点到达汇点的“距离最短”的路径,“距离”使用该路径上的边的单位费用之和来衡量。 
(2)然后找出这条路径上的边的容量的最小值f,则当前最大流max_flow扩充f,同时当前最小费用min_cost扩充 f*min_dist(s,t)。 
(3)将这条路径上的每条正向边的容量都减少f,每条反向边的容量都增加f。 
(4)重复(1)--(3)直到无法找到从源点到达汇点的路径。

需要注意几点: 
1、注意超级源点和超级终点的建立。 
2、初始化时,正向边的单位流量费用为cost[u][v],那么反向边的单位流量费用就为-cost[u][v]。因为回流费用减少。 
3、费用cost数组和容量cap数组每次都要初始化为0。

    求解从源点到汇点的“最短”路径时,由于网络中存在负权边,因此使用SPFA来实现。

实现

#define INFINITE 1 << 26
#define MAX_NODE 1005
#define MAX_EDGE_NUM 40005
struct Edge{
    int to;
    int vol;
    int cost;
    int next;
};
Edge gEdges[MAX_EDGE_NUM];
 
int gHead[MAX_NODE];
int gPre[MAX_NODE];
int gPath[MAX_NODE];
int gDist[MAX_NODE];
 
int gEdgeCount;
void InsertEdge(int u, int v, int vol, int cost){
    gEdges[gEdgeCount].to = v;
    gEdges[gEdgeCount].vol = vol;
    gEdges[gEdgeCount].cost = cost;
    gEdges[gEdgeCount].next = gHead[u];
    gHead[u] = gEdgeCount++;
 
    gEdges[gEdgeCount].to = u;
    gEdges[gEdgeCount].vol = 0;         //vol为0,表示开始时候,该边的反向不通
    gEdges[gEdgeCount].cost = -cost;    //cost 为正向边的cost相反数,这是为了
    gEdges[gEdgeCount].next = gHead[v];
    gHead[v] = gEdgeCount++;
}
 
//假设图中不存在负权和环,SPFA算法找到最短路径/从源点s到终点t所经过边的cost之和最小的路径
bool Spfa(int s, int t){
    memset(gPre, -1, sizeof(gPre));
    memset(gDist, 0x7F, sizeof(gDist));
    gDist[s] = 0;
    queue<int> Q;
    Q.push(s);
    while (!Q.empty()){//由于不存在负权和环,因此一定会结束
        int u = Q.front();
        Q.pop();
 
        for (int e = gHead[u]; e != -1; e = gEdges[e].next){
            int v = gEdges[e].to;
            if (gEdges[e].vol > 0 && gDist[u] + gEdges[e].cost < gDist[v]){
                gDist[v] = gDist[u] + gEdges[e].cost;
                gPre[v] = u; //前一个点
                gPath[v] = e;//该点连接的前一个边
                Q.push(v);
            }
        }
    }
 
    if (gPre[t] == -1)  //若终点t没有设置pre,说明不存在到达终点t的路径
        return false;
    return true;
}
 
int MinCostFlow(int s, int t){
    int cost = 0;
    int flow = 0;
    while (Spfa(s, t)){
        int f = INFINITE;
        for (int u = t; u != s; u = gPre[u]){
            if (gEdges[gPath[u]].vol < f)
                f = gEdges[gPath[u]].vol;
        }
        flow += f;
        cost += gDist[t] * f;
        for (int u = t; u != s; u = gPre[u]){
            gEdges[gPath[u]].vol -= f;   //正向边容量减少
            gEdges[gPath[u]^1].vol += f; //反向边容量增加
        }
    }
    return cost;
}

习题:

poj-2135 

解析 题目是无向图 求两条最短路径的长度 ,两条路径不允许有相交边。其实就是求一条最短路,把边删掉,再找一条。其实可以转化为网络流来写。

无向图把我们建边要建两次ab,ba;ba,ab(模板可以有平行边)   把每个边的容量设为1 花费为边的长度。然后找两次增广路退出来的花费就是答案。

也可以建立超源点汇点 0,n+1   建立两条边  0—1 容量为2 花费为0   n—n+1 容量为2 花费为0   跑一边最小费用就是答案了

code:

#include<algorithm>
#include<cstdio>
#include<cstring>
#include<string>
#include<iostream>
#include<queue>
using namespace std;
#define N 1005
const int inf = 0x3f3f3f3f;
int n, m;
int cnt;
int head[N], vis[N], dis[N], pre[N];
struct eg {
	int to, next, wt, vt, st;
}a[40*N];
void add(int u, int v, int w, int vi) {
	a[cnt].to = v;
	a[cnt].wt = w;
	a[cnt].vt = vi;
	a[cnt].st = u;
	a[cnt].next = head[u];
	head[u] = cnt++;
	a[cnt].to = u;
	a[cnt].wt = -w;
	a[cnt].vt = 0;
	a[cnt].st = v;
	a[cnt].next = head[v];
	head[v] = cnt++;
}
bool spfa(int s, int e) {
	for (int i = 0; i <= e; i++)
		dis[i] = inf;
	memset(vis, 0, sizeof(vis));
	memset(pre, -1, sizeof(pre));
	dis[s] = 0;
	queue<int>q;
	q.push(s);
	while (!q.empty()) {
		int cmt = q.front();
		//cout << cmt << endl;
		q.pop();
		vis[cmt] = 0;
		for (int i = head[cmt]; i != -1; i = a[i].next) {
			if (a[i].vt) {
				int t = a[i].to;
				if (dis[t] > dis[cmt] + a[i].wt) {
					dis[t] = dis[cmt] + a[i].wt;
					pre[t] = i;
					if (!vis[t]) {
						vis[t] = 1;
						q.push(t);
					}
				}
			}
		}
	}
	if (dis[e] == inf)
		return false;
	return true;
}
int M(int s, int e) {
	int sum = 0;
	int minc = inf;
	int temp = e;
	while (spfa(s, e)) {
		minc = inf;
		temp = e;
		while (pre[temp] != -1) {
			minc = min(a[pre[temp]].vt, minc);
			temp = a[pre[temp]].st;
		}
		temp = e;
		while (pre[temp] != -1) {
			a[pre[temp]].vt -= minc;
			a[pre[temp] ^ 1].vt += minc;
			temp = a[pre[temp]].st;
		}
		sum += (dis[e] * minc);
	}
	return sum;
}
int main() {
	memset(head, -1, sizeof(head));
	int s, e, l;
	cnt = 0;
	scanf("%d%d", &n, &m);
	for (int i = 0; i < m; i++) {
		scanf("%d%d%d", &s, &e, &l);
		add(s, e, l, 1);
		add(e, s, l, 1);
	}
	add(0, 1, 0, 2);
	add(n, n + 1, 0, 2);
	cout << M(0, n + 1) << endl;
	return 0;
}

poj-2195

题目大意

    一个nxm的地图,地图上的横纵交错成nxm个交叉点,其中有k个交叉点为房间,k个交叉点为k个小人的初始位置。小人可以在地图上沿着水平或垂直方向行走,每走一步的代价为1。求这k个小人分别到达k个不同的房间,所花费的总代价的最小值。

题目分析

    k个小人走到k个房间节点,走出k条不同的路径,形成一个网络,求出花费最少的k条路径。每个房间只能容纳一个小人,视为小人节点到房间节点的路径上的容量为1,这样就不会出现多个小人挤到同一个房间。那么可以将问题转化为网络流: 
    添加源点s和汇点t,从s出发引出k条边分别到达k个小人,边的容量为1,费用为0;从k个房间节点分别引出一条边到达t,边的容量为1,费用为0;从k个小人节点分别引出k条边达到k个房间节点,边的容量为1,单位费用为小人到房间的最短距离。这样就构造出了一个网络流图,然后求解从源点s到达汇点t的最小费用最大流。

code:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
#include<queue>
using namespace std;
#define N 100005
const int inf = 0x3f3f3f;
struct node {
	int x, y;
};
struct edge {
	int to;int s;int v;int f;
	int next;
};
int n, m;
int cnt = 0, cmt = 0, ans = 0;
node home[N], man[N];
edge a[N];
int head[N], dis[N], vis[N], pre[N];
void bul(int s, int t, int w) {
	a[ans].s = s;
	a[ans].to = t;
	a[ans].v = w;
	a[ans].f = 1;
	a[ans].next = head[s];
	head[s] = ans++;

	a[ans].s = t;
	a[ans].to = s;
	a[ans].v = -w;
	a[ans].f = 0;
	a[ans].next = head[t];
	head[t] = ans++;
}
bool spfa(int s, int t) {
	for (int i = 0; i <= t; i++)
		dis[i] = inf;
	memset(vis, 0, sizeof(vis));
	memset(pre, -1, sizeof(pre));
	queue<int>q;
	q.push(s);
	vis[s] = 1;
	dis[s] = 0;
	while (!q.empty()) {
		int temp = q.front();
		q.pop();
		vis[temp] = 0;
		for (int i = head[temp]; i != -1; i = a[i].next) {
			int ts = a[i].to;
			if (a[i].f&&dis[temp] + a[i].v < dis[ts]) {
				dis[ts] = dis[temp] + a[i].v;
				pre[ts] = i;
				if (!vis[ts]) {
					vis[ts] = 1;
					q.push(ts);
				}
			}
		}
	}
	if (dis[t] == inf)
		return false;
	return true;
}
int MY(int s, int t) {
	int temp;
	int sum_a=0, mx;
	while (spfa(s, t)) {
		mx = inf;
		temp = t;
		while (pre[temp] != -1) {
			mx = min(mx, a[pre[temp]].f);
			temp = a[pre[temp]].s;
		}
		temp = t;
		while (pre[temp] != -1) {
			a[pre[temp]].f -= mx;
			a[pre[temp] ^ 1].f += mx;
			temp = a[pre[temp]].s;
		}
		sum_a += dis[t];
	}
	return sum_a;
}
int main() {
	string strs;
	while(1){
		cnt = 0, cmt = 0, ans = 0;
		memset(head, -1, sizeof(head));
		scanf("%d%d", &n, &m);
		if (n == m & n == 0)
			break;
		for (int i = 0; i < n; i++) {
			cin >> strs;
			for (int j = 0; j < m; j++) {
				if (strs[j] == 'H') {
					home[cnt].x = i;
					home[cnt++].y = j;
				}
				else if (strs[j] == 'm') {
					man[cmt].x = i;
					man[cmt++].y = j;
				}
			}
		}
		for (int i = 0; i < cmt; i++) {
			for (int j = 0; j < cnt; j++) {
				bul(i + 1, cnt + j + 1, abs(home[i].x - man[j].x) + abs(home[i].y - man[j].y));
			}
		}
		for (int i = 1; i <= cnt; i++)
			bul(0, i, 0);
		for (int i = cnt + 1; i <= cnt + cmt; i++)
			bul(i, 2 * cnt + 1, 0);
		cout << MY(0, 2 * cnt + 1) << endl;
	}
	return 0;
}

此题km解法:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
using namespace std;
#define N 1005
#define inf 0x3f3f3f
struct node {
	int x, y;
};
node home[N], man[N];
int mp[N][N], match[N], slack[N];
bool vis_man[N], vis_home[N];
int ex_man[N], ex_home[N];
int cnt, cmt, ans;
int n, m;
bool dfs(int u) {
	vis_man[u] = true;
	for (int i = 1; i <= cnt; i++) {
		if (vis_home[i])continue;
		int gap = ex_home[i] + ex_man[u] - mp[u][i];
		if (gap==0) {
			vis_home[i] = true;
			if (match[i] == -1 || dfs(match[i])) {
				match[i] = u;
				return true;
			}
		}
		else {
			slack[i] = min(gap, slack[i]);
		}
	}
	return false;
}
int KM() {
	memset(match, -1, sizeof(match));
	memset(ex_home, 0, sizeof(ex_home));
	for (int i = 1; i <= cnt; i++) {
		ex_man[i] = mp[i][1];
		for (int j = 2; j <= cmt; j++)
			ex_man[i] = max(ex_man[i], mp[i][j]);
	}
	for (int i = 1; i <= cnt; i++) {
		memset(slack, inf, sizeof(slack));
		while (1) {
			memset(vis_man, false, sizeof vis_man);
			memset(vis_home, false, sizeof vis_home);
			if (dfs(i))break;
			int d = inf;
			for (int j = 1; j <= cmt; j++) {
				if (!vis_home[j])d = min(d, slack[j]);
			}
			for (int j = 1; j <= cnt; j++) {
				if (vis_man[j])ex_man[j] -= d;
				if (vis_home[j])ex_home[j] += d;
				else slack[j] -= d;
			}
		}
	}
	int res = 0;
	for (int i = 1; i <= cnt; i++) {
		res += mp[match[i]][i];
	}
	return -res;
}
int main() {
	string strs;
	while (1) {
		cnt = 0, cmt = 0, ans = 0;
		scanf("%d%d", &n, &m);
		if (n == m & n == 0)
			break;
		for (int i = 0; i < n; i++) {
			cin >> strs;
			for (int j = 0; j < m; j++) {
				if (strs[j] == 'H') {
					home[cnt].x = i;
					home[cnt++].y = j;
				}
				else if (strs[j] == 'm') {
					man[cmt].x = i;
					man[cmt++].y = j;
				}
			}
		}
		for (int i = 1; i <= cmt; i++) {
			for (int j = 1; j <= cnt; j++) {
				mp[i][j] = -(abs(man[i-1].x - home[j-1].x) + abs(man[i-1].y - home[j-1].y));
			}
		}
		cout << KM() << endl;
	}
	return 0;
}

poj-2516

题意:
有N个店主,M个供应商,K种物品。每个供应商对每种物品的的供应量和每个店主对每种物品的需求量的已知。不同的供应商运送不同的货物到不同的店主需要不同的花费。已知从供应商j送第k种货物的单位数量到店主i手上所需的单位花费。
问:供应是否满足需求?如果满足,输出最小花费。如果不满足,输出-1。
输出解析:

题解:
因为涉及N,M,K三种元素,而我们平常做的都是涉及两种元素的题,所以我们很容易想到取出其中的两类元素来当成普通的
最小花费最大流题做,再将多个结果相加。
此题取N,M两个元素,将K个结果相加,我们要建立一个超级源点一个超级汇点,让超级源点与m个仓库相连权值为0,流为每个仓库第k个商品的库存,仓库与n个店家相连权值为从该仓库运送到这个店的花费,流为无限大,再让n个商家与超级汇点相连权值为0,流为该店家需要的量。

code:

#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
#include<cstring>
using namespace std;
#define N 1005
const int inf = 0x3f3f3f;
int n, m, k, cnt;
struct ed {
	int to, next, wt, st, vt;
}a[10*N];
int head[N], dis[N], vis[N], pre[N];
void add(int u, int v, int w, int vi) {
	a[cnt].to = v;
	a[cnt].st = u;
	a[cnt].vt = vi;
	a[cnt].wt = w;
	a[cnt].next = head[u];
	head[u] = cnt++;
	a[cnt].to = u;
	a[cnt].st = v;
	a[cnt].vt = 0;
	a[cnt].wt = -w;
	a[cnt].next = head[v];
	head[v] = cnt++;
}
bool spfa() {
	memset(pre, -1, sizeof(pre));
	memset(vis, 0, sizeof(vis));
	for (int i = 0; i <= n + m + 1; i++)
		dis[i] = inf;
	queue<int>q; 
	q.push(0);
	dis[0] = 0;
	while (!q.empty()) {
		int cmt = q.front();
		q.pop();
		vis[cmt] = 0;
		for (int i = head[cmt]; i != -1; i = a[i].next) {
			if (a[i].vt) {
				int t = a[i].to;
				if (dis[t] > dis[cmt] + a[i].wt) {
					dis[t] = dis[cmt] + a[i].wt;
					pre[t] = i;
					if (!vis[t]) {
						vis[t] = 1;
						q.push(t);
					}
				}
			}
		}
	}
	if (dis[n + m + 1] == inf)return false;
	return true;
}
int MY(int s,int t) {
	int sum = 0;
	int minc, temp;
	while (spfa()) {
		minc = inf;
		temp = t;
		while (pre[temp] != -1) {
			minc = min(minc, a[pre[temp]].vt);
			temp = a[pre[temp]].st;
		}
		temp = t;
		while (pre[temp] != -1) {
			a[pre[temp]].vt -= minc;
			a[pre[temp] ^ 1].vt += minc;
			temp = a[pre[temp]].st;
		}
		sum += dis[t] * minc;
	}
	return sum;
}
int shop[55][55], re[55][55];
int price[55][55][55];
int main() {
	int board[55];
	int sh, r, it;
	int sum;
	while (~scanf("%d%d%d", &n, &m, &k)) {
		if (n == m && m == k && k == 0)
			break;
		memset(board, 0, sizeof(board));
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < k; j++) {
				scanf("%d", &sh);
				board[j] -= sh;
				shop[i][j] = sh;
			}
		}
		for (int i = 0; i < m; i++) {
			for (int j = 0; j < k; j++) {
				scanf("%d", &r);
				board[j] += r;
				re[i][j] = r;
			}
		}
		for (int i = 0; i < k; i++) {
			for (int j = 0; j < n; j++) {
				for (int h = 0; h < m; h++) 
					scanf("%d", &price[i][j][h]);
			}
		}
		int flag = 1;
		for (int i = 0; i < k; i++) {
			if (board[i] < 0) {
				cout << "-1" << endl;
				flag = 0;
				break;
			}
		}
		if (!flag)continue;
		sum = 0;
		for(int i=0;i<k;i++){
			memset(head, -1, sizeof(head));
			cnt = 0;
			for (int j = 0; j < m; j++) {
				add(0, j + 1, 0, re[j][i]);
			}
			for (int j = 0; j < n; j++) {
				for (int h = 0; h < m; h++) {
					add(h + 1, m + j + 1, price[i][j][h], inf);
				}
				add(m + j + 1, m + n + 1, 0, shop[j][i]);
			}
			sum += MY(0, n + m + 1);
		}
		cout << sum << endl;
	}
	return 0;
}

 

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值