版权声明:本文为博主原创文章,禁止所有形式的未经博主允许的转载行为。 https://blog.csdn.net/qq_33330876/article/details/81435113
题目大意
给定一个 n*m 的矩阵 (n,m<=20),保证矩阵内的元素 Ai,j<=20,每次操作可以对矩阵内的数进行 +1/-1。问至少需要多少次操作,可以使矩阵内行与行的和相同,列与列的和相同。
链接:https://www.nowcoder.com/acm/contest/163/I
解题思路
看了一个非官方的题解是用 mcmf 解决的,而且还要对结果进行三分,实在是觉得没有什么三分的必要…假设整个矩阵的和为 K,那么操作后每一行的和一定为 K/n,每一列的和一定为 K/m。我们要得到最小的操作次数,所以操作后矩阵里的数一定不小于原矩阵的最小值,不大于原矩阵的最大值,因此可知 0<=K<=400,且 lcm(n,m) | K。
考虑枚举每一个 K,从源点向表示各个行的点连一条容量为 K/n 的边,从表示各个列的点向汇点连一条容量为 K/m 的边,每个点 Ai,j 表示为一条从 i h行流向 j 列的容量为 Ai,j 的边。对于每一个被枚举的 K,更新答案:ans = min (“+1”操作次数 + “-1”操作次数) = min (K - maxflow + sumA - maxflow) 。
- “+1”操作次数 = K - maxflow。对于这张图,没有流满的流经源汇点的边一定需要中间由点抽象成的边增加容量来补齐。
- “-1”操作次数 = sumA -maxflow。中间的由点抽象成的边如果没有被流满的话,说明流量是冗余的,应该被删去。
代码
#include <bits/stdc++.h>
using namespace std;
inline int read() {
register int val=0, sign=1; char ch;
while(~(ch=getchar()) && (ch<'0' || ch>'9') && ch!='-'); ch=='-'?sign=-1:val=ch-'0';
while(~(ch=getchar()) && (ch>='0' && ch<='9')) val=(val<<1)+(val<<3)+ch-'0';
return val*sign;
}
const int INF=int(1e9)+7;
const int maxd=50;
const int maxe=500;
int tot=0;
int head[maxd];
int S,T;
struct Edge {
int from,to,cap,next;
Edge() {}
Edge(int x,int y,int a,int c):from(x),to(y),cap(a),next(c) {}
}eage[maxe*2];
void add(int x,int y,int a) {
eage[tot]=Edge(x,y,a,head[x]), head[x]=tot++;
eage[tot]=Edge(y,x,0,head[y]), head[y]=tot++;
return;
}
bool used[maxd], vis[maxd];
int dis[maxd];
queue<int> que;
int ans=0;
bool spfa() {
for(int i=0;i<maxd;i++) used[i]=false,dis[i]=INF,vis[i]=false;
while(que.size()) que.pop();
que.push(S);
used[S]=true;
dis[S]=0;
while(que.size()) {
int u=que.front(); que.pop();
used[u]=false;
for(int i=head[u];~i;i=eage[i].next) if(eage[i].cap && dis[eage[i].to]>dis[u]+1) {
int v=eage[i].to;
dis[v]=dis[u]+1;
if(!used[v]) {
used[v]=true;
que.push(v);
}
}
}
return (dis[T]<INF);
}
int dfs(int u,int flow) {
if(u==T || !flow) {
ans+=flow;
return flow;
}
vis[u]=true;
int ret=0;
for(int i=head[u];~i;i=eage[i].next) if(!vis[eage[i].to] && eage[i].cap && dis[eage[i].to]==dis[u]+1) {
int v=eage[i].to;
int newf=dfs(v,min(flow,eage[i].cap));
eage[i].cap-=newf;
eage[i^1].cap+=newf;
ret+=newf;
flow-=newf;
if(!flow) break;
}
if(!ret) dis[u]=-1;
return ret;
}
int Dinic() {
ans=0;
while(spfa()) dfs(S,INF);
return ans;
}
int n,m;
int a[22][22];
int gcd(int n,int m) {return m?gcd(m,n%m):n;}
void init() {
register int i;
for(i=0;i<maxd;++i)
head[i]=-1;
tot=0;
return;
}
int work() {
n=read(), m=read();
register int i,j,k,sum=0, lcm=n*m/gcd(n,m);
for(i=1;i<=n;++i)
for(j=1;j<=m;++j) {
a[i][j]=read();
sum+=a[i][j];
}
S=1;
#define row(k) (S+(k))
#define col(k) (row(n)+(k))
T=col(m)+1;
int res=INF;
for(k=0;k<=n*m*20;k+=lcm) {
init();
for(i=1;i<=n;++i) add(S,row(i),k/n);
for(i=1;i<=m;++i) add(col(i),T,k/m);
for(i=1;i<=n;++i)
for(j=1;j<=m;++j)
add(row(i),col(j),a[i][j]);
Dinic();
res=min(res,k+sum-ans*2);
}
return res;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("input.txt","r",stdin);
// freopen("output.txt","w",stdout);
#endif
int T=read();
for(int i=1;i<=T;++i) {
printf("Case %d: %d\n",i,work());
}
return 0;
}