POJ 2396:Budget (有流量上下界的网络流)
来源:互联网 发布:龙腾会计软件 编辑:程序博客网 时间:2024/06/04 18:41
题目链接:http://poj.org/problem?id=2396
题目意思:
给出一个m行n列的矩阵,矩阵里面的数都是大于0的(输出位置有说明),
给出矩阵每行的和,给出矩阵每列的和,然后给出C个约束条件,x y op num,
op是比较运算符。
如 0 3 > 4 意思是第3列所有数都大于3
如果是 2 0 < 5 意思就是第2行的所有数都小于5
如果是 0 0 > 7 意思就是整个矩阵的数都是大于7的
如果是 1 3 = 8 意思就是1行3列的数是等于8的。
由于输入的时候,可能出现
1 3 > 5
1 3 > 4
这样的情况。
当矩阵某个位置上限有多个的时候,我们应该取最小的上限。
当矩阵某个位置下线有多个的时候,我们应该取最大下限,注意下线最小是0,
因为这个题目其实说的是费用,费用是不能是负数的。
这个题目可以转化为有上下界的网络流的问题。
关于这个问题的讲解,这个链接讲的很清楚很明白:http://blog.csdn.net/regina8023/article/details/45815023
这个题目的解法我是不会的,感觉是挺难的一个题目了,思路来源:http://blog.csdn.net/wsniyufang/article/details/6601162
如果自己搜索得话,这个博文是第一个。
思路是:
建立一个超级源点S(编号0),超级汇点D(编号m+n+1),对于每个边
都有一个上限和下限。上限可以直接看作管道得容量,而下限是每个
管道最少的流水量。行编号采取(1~m),列编号采取(m+1~m+n),
看了第一个链接我们就知道求解这种有上下限的网络流的问题,除了
有超级源点和汇点外,我们还要加一个附加源点x,附加汇点y。我们
给她编号m+n+2 , m+n+3。对于每个边我们用high数组保存其流量上限,
用low数组保存流量下限,用cap数组来保存建成的用来求网络流的图。
#将超级源点和每一行节点相连,相连所的边的上界和下界都是该行所
有数字的和。
#将每一列于超级汇点相连,相连的边的上界和下界都是该列所有数字的和。
#u行v列,流量必须大于w,则下界为w+1.
#u行v列,流量必须小于w,则上界为w-1.
#u行v列,流量必须等于w,则上界和下界都是w.
按照第一个链接所讲的求解思路,其中有一个必要弧的概念,即我们可以
拉出来将每个管道的必要流量拉出来考虑。其实流量上限就是管道的容量,
那么对于u->v的边,我们建立边cap[u][y] = low[u][v] cap[x][v] = low[u][v]
并且此时cap[u][v] = high[u][v] - low[u][v]。并且建立cap[D][S] = INF.
然后我们求从附加源点x到附加汇点y的网络流,如果其流量等于进入y的所
有流量之和记为sum1,首先我们证明了一点,这个网络中的确存在可行的
流可以满足所有的约束条件,但是它未必是最大流,为了求满足条件的最大
流,我们通常会把cap[D][S] 再变成0,再在上面的网络中求从超级源点到超
级汇点的最大流sum2,则最终sum1+sum2是满足所有约束条件的最大流,
但是这个题目它不需要进行两次最大流的操作,因为超级源点S到任意一行
其high数组的值与low数组的值相同,因此求cap的时候,S到任意一行的cap为0,
同理,任意一列到超级汇点T的cap为0,根本就找不到一条S到T的增广路,
所以第二次最大流算法不用跑了,这个题目是找可行流并还要输出数组的值
(意思就是说还需要各个边的流量),high数组保存的是管道容量,cap在求过
最大流后变成最终的残余网络,只需要high和cap位置对应相减就可以得到矩阵
每个位置的值,当然满足题目条件的答案可能不止一个,但是建立网络流模型
可以帮助我们获取其中一个可行解,题目是特殊测试只要我们找到一个可行的解
就可以了。昨天写了一天,WA了好多次,最后终于过了,干了二百多行代码。
AC代码:
#include <iostream>#include <stdio.h>#include <string.h>#include <queue>using namespace std;/**行编号(1~m),列编号(m+1,m+n),超级源点编号(0),超级汇点编号(m+n+1),附加源点编号(m+n+3),附加汇点编号(m+n+2)所以题目中m+n = 240,所以maxn开到240*/ const int maxn = 240;const int INF = 0x3f3f3f3f;///三个数组cap是用来求解网络流的图,low用来存放流量下界,high用来存放流量上界int cap[maxn][maxn],low[maxn][maxn],high[maxn][maxn];///sumrows每行元素的和,sumcols每列元素的和int sumrows[maxn],sumcols[maxn];///求最大流的时候level数组为节点分层,vis深搜的时候标记节点是否已访问int level[maxn],vis[maxn];///sumin[i]流如i节点的流量,sumout[i]流出i节点的流量int sumin[maxn],sumout[maxn];int m,n,c; ///矩阵行,矩阵列,约束条件数量。int maxFlow,minFlow,minFlowNode,sum; /**maxFlow是网络最大流,minFlow增广路中的最小流,minFlowNode是最小流量那个边的起点。sum是所有流量下限的和**/int S,D,x,y; ///S是超级源点,D是超级汇点,x是附加汇点,y是附加源点 ///初始化函数void init(){ memset(high,0,sizeof(high)); ///需要的位置赋值为INF for(int i = 1; i <= m; i++) for(int j = m+1; j <= m+n; j++) high[i][j] = INF; ///流量下限先都设为0,因为最后的那个矩阵是正数 memset(low,0,sizeof(low)); memset(cap,0,sizeof(cap)); memset(sumin,0,sizeof(sumin)); memset(sumout,0,sizeof(sumout));}///数据处理void deal_with_data(int row,int col,char op,int num){ if(row == 0 && col == 0) ///对整个矩阵进行操作 { for(int i = 1; i <= m; i++) for(int j = m+1; j <= m+n; j++) { if(op == '=') low[i][j] = high[i][j] = num; else if(op == '<') high[i][j] = min(high[i][j],num-1); ///取最小上限 else low[i][j] = max(low[i][j],num+1); ///取最大下限 } } else if(row == 0 && col != 0) ///col列共同受该条件约束 { for(int i = 1; i <= m; i++) { if(op == '=') low[i][col+m] = high[i][col+m] = num; else if(op == '<') high[i][col+m] = min(high[i][col+m],num-1); else low[i][col+m] = max(low[i][col+m],num+1); } } else if(row != 0 && col == 0) ///row行共同服从该约束条件 { for(int i = m+1; i <= m+n; i++) { if(op == '=') low[row][i] = high[row][i] = num; else if(op == '<') high[row][i] = min(high[row][i],num-1); else low[row][i] = max(low[row][i],num+1); } } else ///具体的某行某列服从约束条件 { if(op == '=') low[row][col+m] = high[row][col+m] = num; else if(op == '<') high[row][col+m] = min(high[row][col+m],num-1); else low[row][col+m] = max(low[row][col+m],num+1); }}///构建图void buildGraph(){ x = D+1; y = D+2; sum = 0; for(int i = 0; i <= D; i++) for(int j = 0; j <= D; j++) { cap[i][j] = high[i][j]-low[i][j]; ///容量为上下界之差 sumout[i] += low[i][j]; ///从i出的边所有的下界流量和。 sumin[j] += low[i][j]; ///从j入的边所有的下界流量和 sum += low[i][j]; ///所有边的下界流量和 } for(int i = 0; i <= D; i++) ///建立必要弧 { cap[i][x] = sumout[i]; cap[y][i] = sumin[i]; } cap[D][S] = INF;}///广搜分层bool bfs(){ memset(level,-1,sizeof(level)); level[y] = 0; queue<int>qu; qu.push(y); int u; while(!qu.empty()) { u = qu.front(); qu.pop(); for(int i = 0; i <= y; i++) { if(level[i] == -1 && cap[u][i] > 0) { level[i] = level[u] + 1; if(i == x) return true; qu.push(i); } } } return false;}///深搜寻找增广路径void dfs(){ int u,v,cur,i; deque<int>qu; memset(vis,0,sizeof(vis)); vis[y] = 1; qu.push_back(y); while(!qu.empty()) { cur = qu.back(); if(cur == x) { minFlow = INF + 1; minFlowNode = S; for(i = 1; i < qu.size(); i++) { u = qu[i-1]; v = qu[i]; if(cap[u][v]>0 && cap[u][v]<minFlow) { minFlow = cap[u][v]; minFlowNode = u; } } maxFlow += minFlow; for(i = 1; i < qu.size(); i++) { u = qu[i-1]; v = qu[i]; cap[u][v] -= minFlow; cap[v][u] += minFlow; } /**为何要回溯到minFlowNode呢?原因是minFlowNode是当前找到的S-T 中的流量最小的边的起点,则其正向边流量必变为0,这条路断了,我们 就要考虑从minFlowNode找其他的点然后再找增广路径*/ while(!qu.empty() && qu.back() != minFlowNode) { vis[qu.back()] = 0; qu.pop_back(); } } else { for(i = 0; i <= y; i++) { if(cap[cur][i]>0 && level[i]==level[cur]+1 && vis[i]==0) { vis[i] = 1; qu.push_back(i); break; } } if(i > y) qu.pop_back(); } }}///求最大流void dinic(){ maxFlow = 0; while(bfs()) { dfs(); }}///构图void solve(){ buildGraph(); dinic(); if(maxFlow != sum) ///约束条件都满足不了,题目问题不可能有解。 { printf("IMPOSSIBLE\n"); } else { /**由于cap中S到所有行和所有列到D的边容量都是0, 不用第二次求解最大流。问题到此求解结束,输出答案**/ for(int i = 1; i <= m; i++) { printf("%d",high[i][m+1]-cap[i][m+1]); for(int j = m+2; j <= m+n; j++) printf(" %d",high[i][j]-cap[i][j]); printf("\n"); } }}int main(){ int T; scanf("%d",&T); ///T组测试数据 while(T--) { ///m行n列的矩阵 scanf("%d%d",&m,&n); ///输入每行的和 for(int i = 1; i <= m; i++) scanf("%d",&sumrows[i]); ///输入每列的和 for(int i = 1; i <= n; i++) scanf("%d",&sumcols[i]); scanf("%d",&c); int row,col,num; ///行、列、数字 char op; ///比较运算符 init(); ///初始化操作 for(int i = 1; i <= c; i++) { scanf("%d %d %c %d",&row,&col,&op,&num); deal_with_data(row,col,op,num); } S = 0; ///超级源点 D = m+n+1; ///超级汇点 ///超级源点向每行引一条边,其流量上下限是该行元素的和 for(int i = 1; i <= m; i++) low[S][i] = high[S][i] = sumrows[i]; ///每一列向超级汇点引一条边,其流量上下限是该列元素的和 for(int i = m+1; i <= m+n; i++) low[i][D] = high[i][D] = sumcols[i-m]; solve(); if(T) printf("\n"); ///每两组数据间有一行的间隔 } return 0;}
- POJ 2396:Budget (有流量上下界的网络流)
- POJ 2396 Budget 有上下界的网络流
- poj 2396 Budget 有上下界的网络流
- POJ 2396 Budget 有上下界的网络流
- POJ 2396 Budget(有上下界的网络流)
- POJ 2396 Budget 有上下界的可行网络流
- POJ 2396 Budget(无源汇网络有上下界的可行流-Dinic)
- POJ 2396 Budget 上下界网络流
- POJ 2396 Budget 上下界网络流
- POJ 2396 Budget 上下界网络流
- POJ 2396 Budget 有上下界的可行流
- POJ 2396 Budget (上下界网络流)
- poj 2396 Budget (有源汇有上下界的网络流)
- poj 2396 Budget--有源汇+有上下界+可行流
- POJ 2396 Budget 上下界网络流 经典题
- poj 2396 Budget 边容量有上下界的最大流
- poj 2396 Budget 带上下界的网络流模型
- POJ 2396 - Budget 有源汇的上下界可行流
- 以指定的IE浏览器运行页面
- bootstrap 时间插件bootstrap-datetimepicker设置语言
- JavaSE_8系列博客——Java语言的特性(六)--泛型(4)--Java中泛型实现的原理
- Android主题切换(Theme)实现日夜间功能
- 天天写业务代码的程序员,怎么成为技术大牛,开始写技术代码?
- POJ 2396:Budget (有流量上下界的网络流)
- Mysql系列——数据库设计(4)——实体表之间的关系
- 448. Find All Numbers Disappeared in an Array
- 用了下itchat接口。
- java中jdk api等概念的解释
- Oracle12c 安装教程
- 从零搭建React全家桶框架教程
- markdown对图片进行居中、缩放
- Retrofit 源码解析