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





阅读全文
0 0
原创粉丝点击