题目链接: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;
}