Codeforces Edu 160 E. Matrix Problem

文章描述了一个关于矩阵的问题,目标是通过最少操作次数将矩阵中1的数量调整到特定的行和列计数。解决方案是利用网络流模型,构建源点到每一行、每一列的边,并在行列间交换1以节省操作,最终求解最小费用最大流问题。
摘要由CSDN通过智能技术生成

E. Matrix Problem

E

题意

给定一个 01 01 01 矩阵 a a a,大小为 n × m n \times m n×m,以及两个长度分别为 n n n m m m 的数组 A A A B B B

要求用最少的操作次数,将 a a a 修改为符合:

  • ∀ i ∈ [ 1 , n ] ,第 i 行的 1 的数量 = A i \forall i \in [1,n],第 i 行的 1 的数量 = A_i i[1,n],第i行的1的数量=Ai
  • ∀ j ∈ [ 1 , m ] ,第 j 列的 1 的数量 = B j \forall j \in [1,m],第 j 列的1 的数量 = B_j j[1,m],第j列的1的数量=Bj
思路

非常经典的网络流问题,考虑网络流建模:
先将答案初始化为: ∑ a i j \sum a_{ij} aij,表示先将矩阵全部看成 0 0 0,然后尽可能利用原本已经是 1 1 1 的那些位置来产生对行列的贡献,以此来最小化操作次数。

将每一行和每一列抽象出来,的编号为: [ 1 , n ] [1,n] [1,n]的编号为 [ n + 1 , n + m ] [n+1,n+m] [n+1,n+m]
然后对于源点 s s s,向每一行连边 s → i s \rightarrow i si,容量为 A i A_i Ai,费用为 0 0 0
对于汇点 t t t,由每一列向其连边 j → t j \rightarrow t jt,容量为 B j B_j Bj,费用为 0 0 0

对于行列之间的边,如果 a i j = 1 a_{ij} = 1 aij=1:那么连边: i → j + n i \rightarrow j+n ij+n,容量为 1 1 1,费用为 − 1 -1 1,表示把原先多算的钱给节约出去。
a i j = 0 a_{ij} = 0 aij=0:那么边的容量还是 1 1 1,但是费用是 1 1 1,表示要多一次操作将这个位置的 0 0 0 改为 1 1 1

最后跑一个最小费用最大流即可,不难发现答案的存在条件是: M a x F l o w = ∑ A = ∑ B MaxFlow = \sum A = \sum B MaxFlow=A=B,即跑出来的最大流、各行之和,各列之和要相等,都等于最终矩阵中的 1 1 1 的数量

#include<bits/stdc++.h>
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long

const int INF=0x3f3f3f3e;
const long long INFLL=0x3f3f3f3f3f3f4000LL;

typedef long long ll;

const int N = 200;

struct Edge{
    int to;
    int cost;
    int capa;
    int next;
}edge[30000];

int pre[N];
int cnt = 1;
int mp[55][55];
int head[N];
int cost;
int dis[N];
int preve[N];
int sumA,sumB;

void addedge(int u,int v,int capa,int cost){
    edge[++cnt] = {v,cost,capa,head[u]};
    head[u] = cnt;
}

bool SPFA(int s,int t,int cnt){
    std::vector<bool> inq(cnt+1,false);
    fore(i,1,cnt+1){
        dis[i] = INF;
        pre[i] = -1;
    }
    std::queue<int> q;
    q.push(s);
    dis[s] = 0;
    inq[s] = true;
    while(!q.empty()){
        int u = q.front();
        q.pop();
        inq[u] = false;
        for(int i=head[u];~i;i=edge[i].next){
            auto [v,c,capa,ne] = edge[i];
            if(capa <= 0 || dis[u] + c >= dis[v])   continue;
            dis[v] = dis[u] + c;
            pre[v] = u;
            preve[v] = i;
            if(!inq[v]){
                inq[v] = true;
                q.push(v);
            }
        }
    }
    return dis[t] != INF;
}

int mincost(int s,int t,int cnt){
    int MaxFlow = 0;
    while(SPFA(s,t,cnt)){
        int v = t , flow = INF;
        while(~pre[v]) {
            int u = pre[v] , i = preve[v];
            flow = std::min(flow,edge[i].capa);
            v = u;
        }
        v = t;
        while(~pre[v]){
            int u = pre[v] , i = preve[v];
            edge[i].capa -= flow;
            edge[i^1].capa += flow;
            v = u;
        }
        MaxFlow += flow;
        cost += dis[t];
    }
    if(MaxFlow == sumA && sumA == sumB) return cost;
    return -1;
}

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    memset(head,-1,sizeof(head));
    int n,m;
    std::cin>>n>>m;
    fore(i,1,n+1)
        fore(j,1,m+1){
            std::cin>>mp[i][j];
            cost += mp[i][j];
            if(mp[i][j]){
                addedge(i,j+n,1,-1);
                addedge(j+n,i,0,1);
            }            
            else{
                addedge(i,j+n,1,1);
                addedge(j+n,i,0,-1);
            } 
        }

    int s = n + m + 1 , t = s + 1;
    fore(i,1,n+1){
        int a;
        std::cin>>a;
        sumA += a;
        addedge(s,i,a,0);
        addedge(i,s,0,0);
    }
    fore(i,1,m+1){
        int b;
        std::cin>>b;
        sumB += b;
        addedge(i+n,t,b,0);
        addedge(t,i+n,0,0);
    }
    std::cout<<mincost(s,t,n+m+2);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值