POJ 2396:Budget (有流量上下界的网络流)

本文介绍如何使用网络流算法解决一个特殊的矩阵填充问题。针对给定的矩阵及其行和、列和,以及一系列约束条件,通过构建网络流模型找出满足条件的矩阵填充值。文章详细解释了网络流模型的构建过程、关键概念,并提供了完整的AC代码。
摘要由CSDN通过智能技术生成

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





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值