uva 11082 Matrix Decompressing - 最大流

题意:

给你前 i 行和 和前 j 列和 ,还原出这个矩阵


解题思路

首先处理出 每行和 以及 每列和

如果矩阵中的每个数都减一,那么每一行之和都会减少C,每一列都会减少R,这样每个元素的范围变成了0~19。设每一行减一之和为 c[i] ,每一列减一之和为 d[j]。建立一个二分图,每行对应一个X节点,每一列对应一个Y节点,从原点到每一个X节点引一条弧(假设为第一类弧),容量为 c[i] ;从每一个Y节点到汇点引一条弧(假设为第二类弧),容量为 d[j] ;从每一个X节点到每一个Y节点引一条弧(第三类弧),容量为19,。跑一遍最大流,如果第一类弧和第二类弧都满载(流量等于容量),那么每个从X节点到Y节点弧的流量就是矩阵中的元素。

为什么这样是正确的呢?

首先注意到这一点:矩阵中的每个元素都减一。为什么要减一?

这是为了求最大流的方便,题目要求矩阵中的元素要在1到20之间,而在求最大流的时候是有可能出现0流的,所以这样减去一之后求出的流量(矩阵中的元素)在0到19之间,输出时加一就可以了。

那这样建图和求解的原理是什么?

对于每一个X节点,都是只有一个入流,多个出流(分别流向每一个Y节点),显然多个出流的和正好等于入流;同理,对于每一个Y节点,有多个入流(分别来自每一个X节点),只有一个出流,显然多个入流的和等于出流。

再考虑每一类弧的意义:第一类弧,以每一行所有元素之和为容量;第三类弧,以每一列所有元素之和为容量;第二类弧,最终流量为矩阵中的每一个元素。

也就是说,第一类弧分成多个分支,每一个第一类弧都分出一个分支汇到同一个第三类弧。再考虑矩阵:把每一行的和分成多个元素,每一行的和都会分出一个元素排列在同一列,组成这一列的和。其中有很大的相似性,其实就是问题的变形,只要求出每一个第二类弧的流量,就是求出了矩阵中的每一个元素。


代码

大家用的都是刘汝佳的模板,让我这个用挑战程序设计竞赛模板的人心很累啊,感觉自己创造了一个新世界 T_T,完全不能对照着大佬的代码改。。。。。。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <cmath>
#include <string>
#include <map>
#include <set>
#include <queue>
#include <vector>
#include <stack>
#include <cctype>
using namespace std;
typedef unsigned long long ULL;

const int maxn = 300;
const int INF = 0x3f3f3f3f;

bool used[maxn*2];
int n,m;///n行
struct edge{
    int to,cap,rev;
};
vector <edge> G[maxn];

void addedge(int from,int to,int cap){
    G[from].push_back((edge){to,cap,G[to].size()});
    G[to].push_back((edge){from,0,G[from].size()-1});
}

int dfs(int v,int t,int f){
    if(v==t) return f;
    used[v] = 1;
    for(int i =0;i<G[v].size();++i){
        edge &e = G[v][i];
        if(!used[e.to] &&e.cap>0){
            int d = dfs(e.to,t,min(f,e.cap));
            if(d>0){
                e.cap -= d;
                G[e.to][e.rev].cap += d;
                return d;
            }
        }
    }
    return 0;
}

void max_flow(int x,int s,int t){
    while(1){
        memset( used,0,sizeof(used));
        int f = dfs(s,t,INF);
        if(f==0) break;
    }
    printf("Matrix %d\n",x);
    for(int i = 1; i<=n; ++i){
        for(int j = 1; j<G[i].size();++j){///编号为0的边是由行指向原点的那个边
            edge &e = G[i][j];
            printf("%d",G[e.to][e.rev].cap+1);
            if(j<G[i].size()-1) printf(" ");
            else puts("");
        }
    }
}


int main() {

    int t;
	scanf("%d",&t);
	for(int tt = 1; tt<=t;++tt){
        for(int i = 0;i<maxn;++i){
            G[i].clear();
        }
        int sumai[maxn],ai[maxn];
        int sumbi[maxn],bi[maxn];
        sumai[0] = sumbi[0] = 0;

        scanf("%d%d",&n,&m);

        for(int i = 1;i<=n;++i){
            scanf("%d",&sumai[i]);
            ai[i] = sumai[i]-sumai[i-1] - m;
        }

        for(int i = 1;i<=m;++i){
            scanf("%d",&sumbi[i]);
            bi[i] = sumbi[i]-sumbi[i-1] - n;
        }
        ///起点向行连边
        for(int i = 1; i <= n; ++i){
            addedge(0,i,ai[i]);
        }
        ///列向终点连边
        for(int i = 1; i <= m; ++i){
            addedge(n+i,n+m+1,bi[i]);
        }
        ///行向列连边
        for(int i = 1; i <= n; ++i){
            for(int j = 1; j <= m; ++j){
                addedge(i,n+j,19);
            }
        }
        max_flow(tt,0,n+m+1);
	}

    return 0;
}
/*
2
3 4
10 31 58
10 20 37 58
3 4
10 31 58
10 20 37 58
*/


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值