POJ 2516 Minimum Cost 【MCMF:最小费用最大流】

 

/*

第一次做最小代价最大流(MCMF)的题,所谓最小代价最大流就是要找到一个最大流量的方案且这个最大流所需要的费用是所有相等流量中最小的

MCMF和普通的最大流问题的唯一区别是每次找增广路径是要找费用最小的路径,这就需要维护一个费用图,每次增流时需要将这次增流耗费的费用加到总费用上去并修改费用图。

由于商品之间是不相关的,所以对每种商品分别使用MCMF就行,如果有一种商品得不到满足就跳出,如果能一直执行,则最后输出minCost即可

*/

#include <iostream> #define MAX_VAL 99999999 //最大值 #define MIN_VAL -99999999 //最小值 #define MAX_T 55 #define MAX_N 105 using namespace std; int order[MAX_T][MAX_T]; //记录每个商店需要的各种商品的数量 int store[MAX_T][MAX_T]; //记录每个供货站存储的各种商品的数量 int graph[MAX_N][MAX_N]; //残留网络图 int goodsFlow[MAX_T]; //记录每中商品的总需求量 int cost[MAX_N][MAX_N][MAX_T]; //记录某个商店从某个供货站买某件商品需要的价格 int best[MAX_N]; //spfa或者bellmanford算法中维护的最小费用信息 int pre[MAX_N]; //spfa或者bellmanford算法中的记录增广路径 int queued[MAX_N + 1], head, tail; //spfa用到的队列 bool inQueue[MAX_N + 1]; //标记某个节点是否还在队列中 int minCost; //记录全局的总费用 int n, m, k; //输入的n, m, k变量信息 //每轮使用MCMF时需要调用此函数初始化建图 void initGraph(int goodId) { //初始化残留网络, 增加源点0和汇点n + m + 1 memset(graph, 0, sizeof(graph)); int i, j; for(i = 1; i <= n; i++) { //源点到每个商店的权值赋为这个商店对于当前商品的需求量 graph[0][i] = order[i][goodId]; for(j = n + 1; j <= n + m; j++) { //商店i与供货站j之间边的权值赋为商店i对于当前商品的需求量 graph[i][j] = order[i][goodId]; //供货站与汇点之间边的的权值赋为这个供货站对当前商品的库存量 graph[j][n + m + 1] = store[j - n][goodId]; } } } //spfa算法 bool spfa(int goodId) { int i, j; //初始化 for(i = 0; i <= m + n + 1; i++) { pre[i] = -1; best[i] = MAX_VAL; inQueue[i] = false; } //源点入队列 best[0] = 0; queued[tail] = 0; inQueue[0] = true; tail = tail % MAX_N + 1; //当队列不空则持续执行 while(head != tail) //当队列不空则持续执行 { //弹出队首元素 i = queued[head]; head = head % MAX_N + 1; inQueue[i] = false; if(best[i] == MAX_VAL) continue; //访问当前节点的邻居结点 for(j = 0; j <= m + n + 1; j++) { if(j == i) continue; //关键点:松弛的条件是,残留网络中这条边还有剩余流量,这条边在cost费用数组中是存在的,且这样松弛可以更优 if(graph[i][j] > 0 && cost[i][j][goodId] != MIN_VAL && best[i] + cost[i][j][goodId] < best[j]) { //松弛 best[j] = best[i] + cost[i][j][goodId]; pre[j] = i; //被松弛点如果不在队列中则入队列 if(!inQueue[j]) { queued[tail] = j; tail = tail % MAX_N + 1; inQueue[j] = true; } } } } //如果汇点没有被松弛到,则说明已经不存在增广路径了 return best[n + m + 1] != MAX_VAL; } //这里原来使用了bellmanFord算法,后来改为spfa算法,发现效率差的并不多,看来这个不是关键 //其实这题也可以做成二分图的最优匹配,效率肯定会比MCMF高的,这个有时间再弄吧,呼呼 /* bool bellManFord(int goodId) { int i, j; for(i = 0; i <= m + n + 1; i++) { pre[i] = -1; best[i] = MAX_VAL; } best[0] = 0; bool con = true; while(con) { con = false; for(i = 0; i <= m + n + 1; i++) { if(best[i] == MAX_VAL) continue; for(j = 0; j <= m + n + 1; j++) { if(graph[i][j] > 0 && cost[i][j][goodId] != MIN_VAL && best[i] + cost[i][j][goodId] < best[j]) { best[j] = best[i] + cost[i][j][goodId]; pre[j] = i; con = true; } } } } return best[n + m + 1] != MAX_VAL; } */ //MCMF的主函数,求买货物goodId,且总的需求量是totalFlow下是否可以存在一个最小代价的满足方案 bool maxFlowMinCost(int goodId, int totalFlow) { //建图 initGraph(goodId); int totalCost = 0; int flowCount = 0; //能找打增广路径,则持续增流 while(spfa(goodId)) { //取增广路径上的最小流量值 int minFlow = MAX_VAL, preNode, curNode = m + n + 1; while((preNode = pre[curNode]) != -1) { if(graph[preNode][curNode] < minFlow) minFlow = graph[preNode][curNode]; curNode = preNode; } //增加总的流量 flowCount += minFlow; preNode, curNode = m + n + 1; //增流操作 while((preNode = pre[curNode]) != -1) { //对残留网络的修改 graph[preNode][curNode] -= minFlow; graph[curNode][preNode] += minFlow; //对费用图的修改,这个很重要 cost[curNode][preNode][goodId] = - cost[preNode][curNode][goodId]; //计算增加这个流量所需的新费用 totalCost += minFlow * cost[preNode][curNode][goodId]; curNode = preNode; } } minCost += totalCost; //如果已经满足的总流量不等于总的需求流量,则不存在这样一个方案 return flowCount == totalFlow; } int main() { int i, j, t; while(scanf("%d%d%d", &n, &m, &k) && (n + m + k) != 0) { memset(goodsFlow, 0, sizeof(goodsFlow)); minCost = 0; //输入每个商店需要的各种商品的数量 for(i = 1; i <= n; i++) { for(j = 1; j <= k; j++) { scanf("%d", &order[i][j]); goodsFlow[j] += order[i][j]; } } //输入每个供货站存储的各种商品的数量 for(i = 1; i <= m; i++) for(j = 1; j <= k; j++) scanf("%d", &store[i][j]); //建立费用图 for(i = 1; i <= k; i++) { for(j = 0; j <= n + m + 1; j++) { for(t = 0; t <= n + m + 1; t++) { //源点和商店之间的费用为0 if(j == 0 && t != 0 && t != n + m + 1) cost[j][t][i] = 0; //供货站和商店之间的费用也为0 else if(j != 0 && j != n + m + 1 && t == n + m + 1) cost[j][t][i] = 0; //用MIN_VAL标识不存在的边 else cost[j][t + n][i] = MIN_VAL; //商店和供货站之间的费用为输入费用 if(j >= 1 && j <= n && t >= n + 1 && t <= n + m) scanf("%d", &cost[j][t][i]); } } } bool can = true; //遍历所有商品看是否都能满足 for(i = 1; i <= k; i++) { initGraph(i); if(!maxFlowMinCost(i, goodsFlow[i])) { can = false; break; } } if(can) printf("%d/n", minCost); else printf("-1/n"); } return 0; } 

 

你可能感兴趣的:(算法,网络,存储,Graph)