2011.07.24

POJ 2396 Budget

        题意:设有这么一个m*n的矩阵,给定它的每一行的和及每一列的和,以及矩阵中一些元素的上下界限制条件。判断满足这些条件的矩阵是否存在,若不存在输出“IMPOSSIBLE”,若存在,则给出这个矩阵的一种符合要求的形式。

        作为到处推荐的经典构图题,很有意思呢~利用容量有上下限的网络最大流思想,很容易想到把这些条件作为弧。得到模型如下:

1.把矩阵中m*n个元素和m+n个和作为节点,建立源点和汇点。

2.从源点向每行和引容量为行之和的弧,从每列和向汇点引容量为列之和的弧。

3.从每行的和分别向每个元素引一条容量为[0,inf]的弧,从每一个元素向该列的和引出条件如限制的弧。那么如果源点流出的流能全部到达汇点,说明有解。

4.再思考一步,第i行的和经过元素(i,j)流向第j列的和的显然路径是唯一确定的,所以我们可以把弧直接从第i行和的节点引向第j行列的节点,并不会丢失信息,这样可以把图简化到m+n+2个节点,然后用模型解题就可以了。

#include<cstdio>
#include<climits>
#include<cstring>
#define MAXV 300
const int inf=1<<31-1;
int g[MAXV][MAXV],high[MAXV][MAXV],low[MAXV][MAXV],f[MAXV][MAXV];//,c[MAXN][MAXN],f[MAXN][MAXN];
int m,n,x,y,s,t,V,pos,Case;
int vd[MAXV],d[MAXV],in[MAXV],out[MAXV];
inline int min(int a,int b){return a<b?a:b;}
inline int max(int a,int b){return a>b?a:b;}
int dfs(int u,int s,int t,int n,int flow)
{
    int ret,v,tmp;
    if(u == t)return flow;          //找到增广路
    ret = 0;                   //min_d:u在残量网络里相通的边的最小标号距离,初始不能变为maxint
    for(v=1;v<=n;v++)
    if(g[u][v] > 0 && d[u] == d[v] + 1)        //如果是允许弧
    {
        tmp = dfs(v,s,t,n,min(flow-ret,g[u][v]));      //如果找到,增广
        g[u][v] -= tmp;
        g[v][u] += tmp;
        f[u][v]+=tmp;
        f[v][u]-=tmp;
        ret += tmp;
        if(ret == flow)return ret;
    }
    if(d[s] >= n) return ret;         //如果源点的标号距离大于n,即不存在增广路,这一步放在重标号之前
    vd[d[u]]--;
    if(vd[d[u]] == 0)d[s] = n;        //如果该标号距离的顶点是唯一的,那么删除后图出现断层
    d[u]++;
    vd[d[u]]++;
    return ret;
}
int main()
{
    freopen("in","r",stdin);
    freopen("out","w",stdout);
    int i,j,a,b,w,flow,sum,ans,s,t,time,ss,tt;
    char fu;
    scanf("%d",&Case);
    while(Case--){
        memset(g,0,sizeof(g));
        memset(high,0,sizeof(high));
        memset(low,0,sizeof(low));
        memset(in,0,sizeof(in));
        memset(out,0,sizeof(out));
        memset(f,0,sizeof(f));
        memset(d,0,sizeof(d));
        memset(vd,0,sizeof(vd));
        pos=1;sum=ans=0;
        scanf("%d %d\n",&m,&n);
        s=m+n+1;t=s+1;ss=t+1;tt=t+2;          //s,t为源汇点,ss和tt为附加源汇点
        V=t+2;vd[0]=V;
        for(i=1;i<=m;i++)for(j=1;j<=n;j++)
            high[i][j+m]=inf;                 //保存上下界
        for(i=1;i<=m;i++){
            scanf("%d",&w);
            high[s][i]=low[s][i]=w;
        }
        for(j=1;j<=n;j++){
            scanf("%d",&w);
            high[j+m][t]=low[j+m][t]=w;
        }
        for(i=1;i<=m;i++)
            for(j=1;j<=n;j++){
                g[i][m+j]=inf;
            }
        scanf("%d",&time);
        while(time--){
            scanf("%d %d %c %d",&a,&b,&fu,&w);
            if(a!=0 && b!=0)switch(fu){
                case '>' :
                    low[a][b+m]=max(low[a][b+m],w+1);
                    break;
                case '=' :
                    high[a][b+m]=low[a][b+m]=w;
                    break;
                case '<':
                    high[a][b+m]=min(high[a][b+m],w-1);
                    break;
            }
            else if(a==0 && b!=0)switch(fu){
                case '>' :
                    for(i=1;i<=m;i++)low[i][b+m]=max(low[i][b+m],w+1);
                    break;
                case '=' :
                    for(i=1;i<=m;i++)high[i][b+m]=low[i][b+m]=w;
                    break;
               case '<':
                    for(i=1;i<=m;i++)high[i][b+m]=min(high[i][b+m],w-1);
                    break;
            }
            else if(a!=0 && b==0)switch(fu){
                case '>' :
                    for(j=1;j<=n;j++)low[a][j+m]=max(low[a][j+m],w+1);
                    break;
                case '=' :
                    for(j=1;j<=n;j++)high[a][j+m]=low[a][j+m]=w;
                    break;
                case '<':
                    for(j=1;j<=n;j++)high[a][j+m]=min(high[a][j+m],w-1);
                    break;
            }
            else switch(fu){
                case '>' :
                    for(i=1;i<=m;i++)for(j=1;j<=n;j++) low[i][j+m]=max(low[i][j+m],w+1);
                    break;
                case '=' :
                    for(i=1;i<=m;i++)for(j=1;j<=n;j++) high[i][j+m]=low[i][j+m]=w;
                    break;
                case '<':
                    for(i=1;i<=m;i++)for(j=1;j<=n;j++) high[i][j+m]=min(high[i][j+m],w-1);
                    break;
            }
        }     //终于读进来了,然后来开心的yy吧= =
        for(i=1;i<=V-2;i++)for(j=1;j<=V-2;j++){
            g[i][j]=high[i][j]-low[i][j];
            out[i]+=low[i][j];          //in表示流入该节点下界的和,out表示流出该节点下界的和
            in[j]+=low[i][j];
            sum+=low[i][j];
        }
        for(i=1;i<=V-2;i++){
            g[ss][i]=in[i];
            g[i][tt]=out[i];
        }
        g[t][s]=inf;
        while(d[ss]<V){                   //第一次求最大流,判断是否有解
            flow=dfs(ss,ss,tt,V,inf);
            ans+=flow;
        }
        if(ans!=sum){                         //不存在满足上下限的可行流
            printf("IMPOSSIBLE\n\n");
            continue;
        }
        g[t][s]=g[s][t]=0;       //删除tt和ss及必要弧
        V-=2;
        memset(d,0,sizeof(d));
        memset(vd,0,sizeof(vd));
        while(d[s]<V){           //第二次最大流
            flow=dfs(s,s,t,V,inf);
            ans+=flow;
        }
        for(i=1;i<=m;i++){
            for(j=1;j<n;j++)
                printf("%d ",f[i][j+m]+low[i][j+m]);
            printf("%d\n",f[i][m+n]+low[i][j+m]);
        }
        printf("\n");
    }
    return 0;
}
 此处题解受益良多: http://hi.baidu.com/%8E%E1%D0%B3/blog/item/0bc9238072e254b16c811987.html

 这篇也相当清晰:http://acm.hrbeu.edu.cn/forums/index.php?showtopic=2940

 我用的是ISAP算法求最大流,代码貌似算是比较短的,读数太恶心,诶~

 还有一个问题就是,此题在POJ上是Special Judge,不止一种答案。那么OJ是怎么判的呢?是用约束条件来检验,还是有办法可以求出每一种可行的情况呢?如果是后者,应该有很有意思的算法吧…知道的同学求解释lol……

多源多汇的网络流问题:

        添加超级源点,到多个源点引容量为inf的弧;添加超级汇点,从多个汇点向超级汇点引容量为inf的弧。正确性显然。



容量有上下界限制的网络最大流问题:

      首先是可行流的探讨:

1.把以[a,b]为上下界的弧拆成容量为a的必要弧和容量为b-a的非必要弧。

2.建立附加源点ss和附加汇点tt,使tt-ss的容量为inf,把从u-v的必要弧改为u-tt,tt-ss,ss-v,其中u-tt和ss-v的容量同为a。

3.为明确源和汇,去除弧tt-ss,新增弧t-s,容量为inf,s和t为原源汇点。至此附加网络建成。

4.计算ss-tt的最大流,如果从ss出发的sum(ai)的和全部能够到达tt,则说明有符合条件的可行流存在。由必要弧必须饱和,正确性显然。

      证明存在性后我们来算最大流。

5.去除tt和ss,去除弧t-s,在原图上求原网络最大流,需要注意的是必要弧不可以退流,所以可以直接删去,计算结束再加回去即可。


容量有上下界限制的网络的最小流问题:

        1.探讨可行流步骤与上面最大流的步骤1~4完全一致,第5改成以t为源点s为汇点,倒向求解最大流即为所求。

      2.另有一种方法是ZZY大人06年国家集训队提出的算法ORZ:

             建立新源汇点ds dt,对于任意点a:

         连接ds->a,容量等于a为终点的所有的边的下界和;

         连接a->dt,容量等于a为起点的所有的边的下界和;

         这样求一次ds->dt的最大流

        然后原来t->s加流量无穷大的边,再求多次ds->dt的最大流

        判断ds出发所有的边是否都流满了,如果都流满了才有解,否则无解。

        那么t->s上的流量就是最小流。

    ym大神的传送门:http://hi.baidu.com/dragon_eric123/blog/item/00cd5ac981a9b71f7f3e6f24.html


         其实吧这类有上下界限制的网络流问题都可以用二分查找解决,也容易理解。

         传送至XDU上古神牛wm处膜拜:http://www.cppblog.com/RyanWang/archive/2009/08/18/93672.html



ISAP+GAP+邻接矩阵  beta1.1

我又把模板给改了……

int dfs(int u,int s,int t,int n,int flow)
//当前点u,源点s,汇点t,共n个点,当前流flow
{
    int ret,v,tmp;
    if(u == t)return flow;          //找到增广路
    ret = 0;                   //min_d:u在残量网络里相通的边的最小标号距离,初始不能变为maxint
    for(v=1;v<=n;v++)
    if(g[u][v] > 0 && d[u] == d[v] + 1)        //如果是允许弧
    {
        tmp = dfs(v,s,t,n,min(flow-ret,g[u][v]));      //如果找到,增广
        g[u][v] -= tmp;
        g[v][u] += tmp;
        f[u][v]+=tmp;
        f[v][u]-=tmp;
        ret += tmp;
        if(ret == flow)return ret;
    }
    if(d[s] >= n) return ret;         //如果源点的标号距离大于n,即不存在增广路,这一步放在重标号之前
    vd[d[u]]--;
    if(vd[d[u]] == 0)d[s] = n;        //如果该标号距离的顶点是唯一的,那么删除后图出现断层
    d[u]++;
    vd[d[u]]++;
    return ret;
}

调用形式:
while(d[s]<V){           //第二次最大流
            flow=dfs(s,s,t,V,inf);
            ans+=flow;
        }





         话唠:矮油,这么快7月底了啊~你还差的远呢……

         这个故事告诉我们果然什么数据结构的模板都要有一个啊~

         图论题终于开始没有图了,点不是点,边也不一定是边,关键是要熟悉模型性质,弄清本质,套错了多囧啊= =

         大象无形,多有意思的事情呢~

   


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值