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;
}

 

posted @ 2015-10-18 19:54  农民伯伯-Coding  阅读(398)  评论(0编辑  收藏  举报