poj_2396 有上下界的网络流
题目大意
一个mxn的矩阵,给出矩阵中每一行的和sh[1,2...m]以及每一列的数字的和目sv[1,2...n],以及矩阵中的一些元素的范围限制,比如a[1][2] > 1, a[2][3] < 4, a[3][3] = 10等等,且要求矩阵中的每个元素值非负。求出,是否存在满足所有给出的限制条件的矩阵,若存在输出。
题目分析
这么多限制条件。。开始只能想到暴力解法+剪枝。这他妈得需要多大的脑洞才能无中生有的想到网络流解法? 不过,给出网络流的想法之后发现采用网络流解法确实很合理,很简单(唯一不好的是建图太费劲!!)。
考虑将每行视为一个节点,每列视为一个节点,这样就有m个行节点和n个列节点,m个行节点和n个列节点之间的直接通路mxn个,这些通路上的流量视为我们要求的矩阵的元素值。
那么,如何利用限制条件来构造图?
添加一个源点s和一个汇点t。将每一行的数字和视为从s流入该行对应节点的上下界相同的流,每一列的数字和视为从该列对应的节点流到t的上下界相同的流。
然后,将矩阵元素(i, j)的范围限制在图中用行节点i到列节点j之间的一条有流量上下界的边表示。那么,行节点i的流入量(只有s点流入行节点)总和为该行的数字总和,同时该行节点连接到n个列节点,这n条边上的流量分别对应元素[i,1][i,2].... 且若该网络存在可行流我们对图节点流量和矩阵元素求和的对应。
根据矩阵元素的限制条件,构造图,该图为一个有源汇有上下界的网络流。采用求解有源汇流量有上下界的网络可行流的解法:先将有源汇转换为无源汇,然后求解可行流
。
ps: 麻蛋,写了五六个小时,中间各种bug,累感无爱。。 总结出一个规律:在程序求解多个步骤,多种因素对某一个变量的影响的时候,常常可以总结规律,不需要每次有一个因素起作用的时候都修改目标变量,可以先将各个因素/各个步骤的影响记录下来,最后再修改目标变量。
这样做,基本都是可以提高效率(降低时间复杂度和编码复杂度,简化逻辑),甚至有时候这就应该是合理的逻辑。
实现(c++)
#include<stdio.h> #include<string.h> #include<queue> #include<algorithm> using namespace std; #define MAX_NODE 250 #define MAX_EDGE_NUM 50000 #define INFINITE 1 << 25 #define min(a, b) a<b? a:b #define max(a,b) a>b? a:b //============================= 以下为 寻找最大网络流的 ISAP算法 部分 ============================= struct Edge{ //定义边 int from; int to; int w; int next; int rev; bool operator == (const pair<int, int>& p){ return from == p.first && to == p.second; } }; Edge gEdges[MAX_EDGE_NUM]; int gEdgeCount; int gFlow[MAX_NODE][MAX_NODE]; int gGap[MAX_NODE]; int gDist[MAX_NODE]; int gHead[MAX_NODE]; int gPre[MAX_NODE]; int gPath[MAX_NODE]; int ss, tt, sum1, sum2, m, n; int gSource, gDestination; void InsertEdge(int u, int v, int w){ Edge* it = find(gEdges, gEdges + gEdgeCount, pair<int, int>(u, v)); if (it != gEdges + gEdgeCount){ if (u == ss || v == tt){ it->w += w; } else{ it->w = w; } } else{ int e1 = gEdgeCount; gEdges[e1].from = u; gEdges[e1].to = v; gEdges[e1].w = w; gEdges[e1].next = gHead[u]; gHead[u] = e1; gEdgeCount++; int e2 = gEdgeCount; gEdges[e2].from = v; gEdges[e2].to = u; gEdges[e2].w = 0; gEdges[e2].next = gHead[v]; gHead[v] = e2; gEdges[e1].rev = e2; gEdges[e2].rev = e1; gEdgeCount++; } } void Bfs(){ memset(gGap, 0, sizeof(gGap)); memset(gDist, -1, sizeof(gDist)); gGap[0] = 1; gDist[gDestination] = 0; queue<int>Q; Q.push(gDestination); while (!Q.empty()){ int n = Q.front(); Q.pop(); for (int e = gHead[n]; e != -1; e = gEdges[e].next){ int v = gEdges[e].to; if (gDist[v] >= 0) continue; gDist[v] = gDist[n] + 1; gGap[gDist[v]] ++; Q.push(v); } } } int ISAP(int n){ // n为节点的数目 Bfs(); int u = gSource; int e, d; int ans = 0; while (gDist[gSource] <= n){ if (u == gDestination){ //增广 int min_flow = INFINITE; for (e = gPath[u]; u != gSource; e = gPath[u = gPre[u]]){ //注意,先u = gPre[u], 再取 e = gPath[u] min_flow = min(min_flow, gEdges[e].w); } u = gDestination; for (e = gPath[u]; u != gSource; e = gPath[u = gPre[u]]){ gEdges[e].w -= min_flow; gEdges[gEdges[e].rev].w += min_flow; gFlow[gPre[u]][u] += min_flow; gFlow[u][gPre[u]] -= min_flow; //这个不能少,注意!!!! } ans += min_flow; } for (e = gHead[u]; e != -1; e = gEdges[e].next){ if (gEdges[e].w > 0 && gDist[u] == gDist[gEdges[e].to] + 1) break; } if (e >= 0){ //向前推进 gPre[gEdges[e].to] = u; //前一个点 gPath[gEdges[e].to] = e;//该点连接的前一个边 u = gEdges[e].to; } else{ d = n; for (e = gHead[u]; e != -1; e = gEdges[e].next){ if (gEdges[e].w > 0) //需要能够走通才行!! d = min(d, gDist[gEdges[e].to]); } if (--gGap[gDist[u]] == 0) //gap优化 break; gDist[u] = d + 1; //重标号 ++gGap[gDist[u]]; //更新 gap!! if (u != gSource) u = gPre[u];//回溯 } } return ans; } //=================================以上为 ISAP 算法 ============================ //将图打印出来,用于debug void print_graph(int n){ for (int u = 0; u < n; u++){ printf("node %d links to ", u); for (int e = gHead[u]; e != -1; e = gEdges[e].next) printf("%d(flow = %d) ", gEdges[e].to, gEdges[e].w); printf("\n"); } } //保存 第i行第j列的数字的范围 struct Node{ int min_val; int max_val; }; Node gNodes[250][25]; //设置数字的范围 bool SetDataCons(Node& node, char op, int val){ if (op == '='){ if (node.max_val != -1 && node.max_val < val){ return false; } if (node.min_val != -1 && node.min_val > val){ return false; } node.max_val = node.min_val = val; } else if (op == '>'){ if (node.max_val != -1 && node.max_val <= val){ return false; } node.min_val = max(node.min_val, val + 1); } else if (op == '<'){ if (val <= 0){ return false; } if (node.min_val != -1 && node.min_val >= val){ return false; } if (node.max_val != -1) node.max_val = min(node.max_val, val - 1); else node.max_val = val - 1; } return true; } //建图 void BuildGraph(){ //注意,第i行,第j列,对应node结构体的 gNodes[i][j],并且对应,最后的图中的节点为 i和 j+m(m为行数) for (int i = 1; i <= m; i++){ for (int j = 1; j <= n; j++){ if (gNodes[i][j].max_val == -1){ if (gNodes[i][j].min_val == -1){ InsertEdge(i, j + m, INFINITE); } else{ sum1 += gNodes[i][j].min_val; InsertEdge(i, tt, gNodes[i][j].min_val); InsertEdge(ss, j + m, gNodes[i][j].min_val); InsertEdge(i, j + m, INFINITE); gFlow[i][j + m] = gNodes[i][j].min_val; //边的下限,先加入到最后的边的流量中 } } else { if (gNodes[i][j].min_val == -1){ InsertEdge(i, j + m, gNodes[i][j].max_val); } else{ sum1 += gNodes[i][j].min_val; InsertEdge(i, tt, gNodes[i][j].min_val); InsertEdge(ss, j + m, gNodes[i][j].min_val); if (gNodes[i][j].max_val > gNodes[i][j].min_val) InsertEdge(i, j + m, gNodes[i][j].max_val - gNodes[i][j].min_val); gFlow[i][j + m] = gNodes[i][j].min_val;//边的下限,先加入到最后的边的流量中 } } } } } int main(){ int c, w, u, v, cas, val; char op; scanf("%d", &cas); while (cas --){ scanf("%d %d", &m, &n); memset(gNodes, -1, sizeof(gNodes)); gEdgeCount = 0; memset(gEdges, 0, sizeof(gEdges)); memset(gFlow, 0, sizeof(gFlow)); memset(gHead, -1, sizeof(gHead)); gSource = 0, gDestination = n + m + 1; ss = n + m + 2, tt = n + m + 3; sum1 = 0, sum2 = 0; bool valid_data = true; //输入的过程中,先插入一些边 从 SS 出发的 for (int i = 1; i <= m; i++){ scanf("%d", &w); if (valid_data == false) continue; if (w < 0){ valid_data = false; } sum1 += w; InsertEdge(ss, i, w); } InsertEdge(gSource, tt, sum1); //输入的过程中,先插入一些边 到达 TT的 for (int i = m + 1; i <= m + n; i++){ scanf("%d", &w); if (valid_data == false) continue; if (w < 0) valid_data = false; sum2 += w; InsertEdge(i, tt, w); } InsertEdge(ss, gDestination, sum2); if (sum1 != sum2){ valid_data = false; } sum1 = sum2 = (sum1 + sum2); //设置 矩阵中数字的范围 scanf("%d", &c); for (int i = 0; i < c; i++){ scanf("%d %d %c %d", &u, &v, &op, &val); if (valid_data == false) continue; if (w < 0){ if (op == '=' || op == '<') valid_data = false; continue; } if (u == 0){ if (v == 0){ for (int i = 1; i <= m; i++){ for (int j = 1; j <= n; j++){ if (valid_data == false){ break; } valid_data = SetDataCons(gNodes[i][j],op, val); } } } else{ for (int i = 1; i <= m; i++){ if (valid_data == false){ break; } valid_data = SetDataCons(gNodes[i][v], op, val); } } } else{ if (v == 0){ for (int j = 1; j <= n; j++){ if (valid_data == false){ break; } valid_data = SetDataCons(gNodes[u][j], op, val); } } else{ valid_data = SetDataCons(gNodes[u][v], op, val); } } } if (valid_data == false){ printf("IMPOSSIBLE\n\n"); continue; } //插入 t-->s的边,容量无穷大,使得有源汇的网络变为无源汇的网络 InsertEdge(gDestination, gSource, INFINITE); //建图 BuildGraph(); gSource = ss; gDestination = tt; // print_graph(n + m + 4); //找最大流 int ans = ISAP(n + m + 4); if (ans != sum1){ printf("IMPOSSIBLE\n\n"); continue; } //输出各个边的流量, 从图中节点i到节点j的流量,即为 矩阵 中 [i][j-m]的数字的大小 for (int i = 1; i <= m; i++){ for (int j = m+1; j <= m+n; j++){ printf("%d ", gFlow[i][j]); } printf("\n"); } printf("\n"); } return 0; }