E. Matrix Problem
题意
给定一个 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
s→i,容量为
A
i
A_i
Ai,费用为
0
0
0
对于汇点
t
t
t,由每一列向其连边
j
→
t
j \rightarrow t
j→t,容量为
B
j
B_j
Bj,费用为
0
0
0
对于行列之间的边,如果
a
i
j
=
1
a_{ij} = 1
aij=1:那么连边:
i
→
j
+
n
i \rightarrow j+n
i→j+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;
}