[ARC135D] Add to Square
对网格 \(A\)黑白染色,黑色位置正负取反。这样操作就变为,左上右下加 \(x\),右上左下减 \(x\)。
记 \(sx_i=\sum_{j=1}^m A_{i,j}\),\(sy_j=\sum_{i=1}^n A_{i,j}\)。
容易发现,任意行列的和都与 \(A\) 相同的所有矩阵都是可达的。
接下来考虑怎么算出最小的最终矩阵 \(B\)。
首先有:答案不小于 \(Sx=\sum_{i=1}^n |sx_i|\),也不小于 \(Sy=\sum_{j=1}^m |sy_j|\)。
我们尝试证明答案能取到 \(\max(Sx,Sy)\) 这个下界。
把 \(sx_i\) 看做需要给行 \(i\) 分配 \(sx_i\) 的总权值,\(sy_j\) 同理。
- 那么每次如果存在一个 \(sx_i\) 和 \(sy_j\) 同号则在 \(B_{i,j}\) 算上 \(\min(|sx_i|,|sy_j|)\) 的贡献(正负符号取决于 \(sx,sy\))。
- 如果不存在同号则操作一对 \(sx_{u}>0,sx_{v}<0\) 或一对 \(sy_u>0,sy_u<0\),直至所有 \(sx,sy\) 都为 \(0\)。
上述过程可以理解为,操作一对 \(sx_i,sy_j\) 的时候,是给 \(Sx,Sy\) 都减去一个数。而开始操作两行/两列的时候,就等价于 \(Sx,Sy\) 中的一个已经减到 \(0\) 了(因为 \(\sum_{i=1}^n sx_i=\sum_{j=1}^m sy_j\))
点击查看代码
#include <bits/stdc++.h>
#define FL(i, a, b) for (int i = (a); i <= (b); ++i)
#define FR(i, a, b) for (int i = (a); i >= (b); --i)
using namespace std;
typedef long long ll;
const int N = 510;
int n, m;
ll sx[N], sy[N], B[N][N];
bool Work() {
int px = 0, nx = 0;
int py = 0, ny = 0;
FL(i, 1, n) {
if (sx[i] > 0) px = i;
if (sx[i] < 0) nx = i;
}
FL(i, 1, m) {
if (sy[i] > 0) py = i;
if (sy[i] < 0) ny = i;
}
if (px && py) {
ll t = min(sx[px], sy[py]);
B[px][py] += t;
sx[px] -= t, sy[py] -= t;
return 1;
}
if (nx && ny) {
ll t = max(sx[nx], sy[ny]);
B[nx][ny] += t;
sx[nx] -= t, sy[ny] -= t;
return 1;
}
if (px && nx) {
ll t = min(sx[px], -sx[nx]);
B[px][1] += t, B[nx][1] -= t;
sx[px] -= t, sx[nx] += t;
return 1;
}
if (py && ny) {
ll t = min(sy[py], -sy[ny]);
B[1][py] += t, B[1][ny] -= t;
sy[py] -= t, sy[ny] += t;
return 1;
}
return 0;
}
int main() {
scanf("%d %d", &n, &m);
FL(i, 1, n) {
FL(j, 1, m) {
int w;
scanf("%d", &w);
if ((i + j) & 1)
w = -w;
sx[i] += w;
sy[j] += w;
}
}
ll Sx = 0, Sy = 0;
FL(i, 1, n) {
Sx += abs(sx[i]);
}
FL(i, 1, m) {
Sy += abs(sy[i]);
}
while (Work());
printf("%lld\n", max(Sx, Sy));
FL(i, 1, n) {
FL(j, 1, m) {
if ((i + j) & 1)
B[i][j] = -B[i][j];
printf("%lld%c", B[i][j], " \n"[j == m]);
}
}
return 0;
}

浙公网安备 33010602011771号