P7515 [省选联考 2021 A 卷] 矩阵游戏 题解

Description

Alice 有一个 n×mn \times m 的矩阵 ai,ja_{i, j}1in1 \le i \le n1jm1 \le j \le m),其每个元素为大小不超过 106{10}^6 的非负整数。

Bob 根据该矩阵生成了一个 (n1)×(m1)(n - 1) \times (m - 1) 的矩阵 bi,jb_{i, j}1in11 \le i \le n - 11jm11 \le j \le m - 1),每个元素的生成公式为

bi,j=ai,j+ai,j+1+ai+1,j+ai+1,j+1b_{i, j} = a_{i, j} + a_{i, j + 1} + a_{i + 1, j} + a_{i + 1, j + 1}

现在 Alice 忘记了矩阵 ai,ja_{i, j},请你根据 Bob 给出的矩阵 bi,jb_{i, j} 还原出 ai,ja_{i, j}

2n,m3002 \le n, m \le 3000bi,j4×1060 \le b_{i, j} \le 4 \times {10}^6

Solution

首先有个显然的事实是只要确定了第一行和第一列的值就能确定整个矩阵。

那么可以先在第一行和第一列随便填数,这时可能会有些数不在限制里面,考虑调整。

注意到让一行或一列进行类似 x,+x,x,+xx,+x-x,+x,-x,+x\ldots-x,+x 的操作后仍然满足 bb 矩阵的性质,不妨设 xx 为偏移量。

考虑只做这类操作,设 xix_i 为第 ii 行的偏移量,yiy_i 为第 ii 列的偏移量。

那么对于每个 (i,j)(i,j),一定满足 0ai,j+(1)jxi+(1)iyj1060\leq a_{i,j}+(-1)^jx_i+(-1)^iy_j\leq 10^6。可以设 bi=(1)ixi,ci=(1)i+1yib_i=(-1)^ix_i,c_i=(-1)^{i+1}y_i,那么就是 0(1)i+jbi(1)i+jcj1060\leq (-1)^{i+j}b_i-(-1)^{i+j}c_j\leq 10^6,这是个差分约束的形式,可以 spfa 求解。无解就等价于出现了负环。

下面证明一下通过上面所述的操作一定可以操作出任意符合条件的矩形:

由于只要确定了第一行和第一列的值就可以确定整个矩阵,所以可以先把行操作做完再做列操作,容易发现这样第一行第一列的数可以取到任意值,也就是说这样做可以构造出任意一个符合条件的矩阵。

时间复杂度:O(n3)O(n^3)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <bits/stdc++.h>

#define int int64_t

const int kMaxN = 305;

int n, m;
int a[kMaxN][kMaxN], b[kMaxN][kMaxN], dis[kMaxN * 2], cnt[kMaxN * 2];
bool inq[kMaxN * 2];
std::vector<std::pair<int, int>> G[kMaxN * 2];

bool spfa() {
std::queue<int> q;
for (int i = 1; i <= n + m; ++i)
dis[i] = 1e9, cnt[i] = inq[i] = 0;
q.emplace(1), dis[1] = 0, inq[1] = 1;
for (; !q.empty();) {
int u = q.front(); q.pop();
inq[u] = 0;
for (auto [v, w] : G[u]) {
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
if (!inq[v]) q.emplace(v), inq[v] = 1;
if (++cnt[v] == n + m) return 0;
}
}
}
return 1;
}

void dickdreamer() {
std::cin >> n >> m;
for (int i = 1; i <= n + m; ++i) G[i].clear();
for (int i = 1; i < n; ++i)
for (int j = 1; j < m; ++j)
std::cin >> b[i][j];
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
if (i == 1 || j == 1) a[i][j] = 0;
else a[i][j] = b[i - 1][j - 1] - a[i - 1][j - 1] - a[i - 1][j] - a[i][j - 1];
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
int L = -a[i][j], R = 1e6 - a[i][j];
if ((i + j) & 1) std::swap(L, R), L = -L, R = -R;
G[i].emplace_back(j + n, -L), G[j + n].emplace_back(i, R);
}
}
if (!spfa()) return void(std::cout << "NO\n");
std::cout << "YES\n";
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
int op = ((i + j) % 2 ? -1 : 1);
std::cout << a[i][j] + dis[i] * op - dis[j + n] * op << ' ';
}
std::cout << '\n';
}
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

P7515 [省选联考 2021 A 卷] 矩阵游戏 题解
https://sobaliuziao.github.io/2024/08/24/post/5c1857b.html
作者
Egg_laying_master
发布于
2024年8月24日
许可协议