把第 t 天的值看成目标点周围曼哈顿半径 t 菱形区域的 gcd,再按目标值的质因子求最早被周围格子消掉的时间。
OJ: luogu
题目 ID: P7243
难度:提高+/省选-
标签:数学最大公约数思维曼哈顿距离Pollard Rho
日期: 2026-06-19 08:34
题意
有一个 n × m 的矩阵。
一次变换会把每个位置 (i,j) 改成:
- 自己;
- 上下左右存在的格子;
这些数的最大公约数。
现在只问一个位置 (x,y):它最少经过多少次变换会变成 1。
如果无论怎么变都不可能变成 1,输出 -1。
思路
最直接的想法是按天模拟整张矩阵。
这个版本最容易理解题意:
#include <bits/stdc++.h>
using namespace std;
using ull = unsigned long long;
const int MAXN = 15;
int n, m;
ull cur[MAXN][MAXN], nxt[MAXN][MAXN];
int target_x, target_y;
int dx[5] = {0, -1, 1, 0, 0};
int dy[5] = {0, 0, 0, -1, 1};
bool in_board(int x, int y) {
return x >= 1 && x <= n && y >= 1 && y <= m;
}
// 模拟一天,并返回矩阵是否真的发生了变化。
bool transform_once() {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
ull g = cur[i][j];
for (int k = 1; k <= 4; k++) {
int nx = i + dx[k];
int ny = j + dy[k];
if (in_board(nx, ny)) {
g = gcd(g, cur[nx][ny]);
}
}
nxt[i][j] = g;
}
}
bool changed = false;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (cur[i][j] != nxt[i][j]) {
changed = true;
}
cur[i][j] = nxt[i][j];
}
}
return changed;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> cur[i][j];
}
}
cin >> target_x >> target_y;
// 曼哈顿半径超过整个棋盘后,答案就不会再变化了。
for (int day = 0; day <= n + m; day++) {
if (cur[target_x][target_y] == 1) {
cout << day << '\n';
return 0;
}
if (!transform_once()) {
break;
}
}
cout << -1 << '\n';
return 0;
}但这题的数据范围很大,不能真的一天一天推。
第 t 天对应一个曼哈顿菱形
先看第 1 天,目标点 (x,y) 取的是:
- 自己;
- 上下左右。
这正是一个曼哈顿距离不超过 1 的区域。
再看第 2 天,它取的是这几个点第 1 天结果的 gcd。由于 gcd 具有结合律和交换律,所以最终等价于把更大一圈的初始格子全部一起取 gcd。
继续归纳下去,可以得到:
第 t 天时,(x,y) 的值等于所有满足
|i-x| + |j-y| <= t
的初始格子的整体 gcd。
只需要关心目标值的质因子
设目标点初始值是 v = a[x][y]。
以后每一天的值都只会不断变小,所以它的质因子只会消失,不可能新出现。
因此我们只需要关心 v 的不同质因子。
设其中一个质因子是 p。
如果半径 t 的菱形里所有数都还能被 p 整除,那么第 t 天后的 gcd 里还会保留 p。
反过来,只要这个菱形里出现了一个不能被 p 整除的格子,那么 p 就被消掉了。
所以对于每个质因子 p,只要求出:
离 (x,y) 最近的、满足 a[i][j] % p != 0 的格子的曼哈顿距离 d_p。
那么:
- 当
t < d_p时,p还在; - 当
t >= d_p时,p已经消失。
最终值要变成 1,就要求所有质因子都消失。
所以答案就是:
所有 d_p 的最大值。
如果某个质因子在整张矩阵里都找不到“不能整除它”的格子,那么它永远不会消失,答案就是 -1。
为什么要用 Pollard Rho
因为 a[i][j] 最大可达 10^18,而我们只需要分解目标点 a[x][y] 的质因子。
这个范围下,用 Miller Rabin + Pollard Rho 提取不同质因子是比较稳妥的做法。
代码
#include <bits/stdc++.h>
using namespace std;
using ull = unsigned long long;
using u128 = __uint128_t;
const int MAXN = 2005;
int n, m;
ull a[MAXN][MAXN];
int target_x, target_y;
mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
ull mul_mod(ull a, ull b, ull mod) {
return (u128) a * b % mod;
}
ull pow_mod(ull a, ull b, ull mod) {
ull res = 1;
while (b) {
if (b & 1) {
res = mul_mod(res, a, mod);
}
a = mul_mod(a, a, mod);
b >>= 1;
}
return res;
}
bool miller_rabin(ull n) {
if (n < 2) {
return false;
}
for (ull p : {2ULL, 3ULL, 5ULL, 7ULL, 11ULL, 13ULL, 17ULL, 19ULL, 23ULL, 29ULL, 31ULL, 37ULL}) {
if (n % p == 0) {
return n == p;
}
}
ull d = n - 1;
int s = 0;
while ((d & 1) == 0) {
d >>= 1;
s++;
}
for (ull a : {2ULL, 325ULL, 9375ULL, 28178ULL, 450775ULL, 9780504ULL, 1795265022ULL}) {
if (a % n == 0) {
continue;
}
ull x = pow_mod(a, d, n);
if (x == 1 || x == n - 1) {
continue;
}
bool ok = false;
for (int r = 1; r < s; r++) {
x = mul_mod(x, x, n);
if (x == n - 1) {
ok = true;
break;
}
}
if (!ok) {
return false;
}
}
return true;
}
ull pollard_rho(ull n) {
if (n % 2 == 0) {
return 2;
}
while (true) {
ull c = uniform_int_distribution<ull>(1, n - 1)(rng);
ull x = uniform_int_distribution<ull>(0, n - 1)(rng);
ull y = x;
ull d = 1;
while (d == 1) {
x = (mul_mod(x, x, n) + c) % n;
y = (mul_mod(y, y, n) + c) % n;
y = (mul_mod(y, y, n) + c) % n;
ull diff = x > y ? x - y : y - x;
d = gcd(diff, n);
}
if (d != n) {
return d;
}
}
}
void factorize(ull n, vector<ull> &fac) {
if (n == 1) {
return;
}
if (miller_rabin(n)) {
fac.push_back(n);
return;
}
ull d = pollard_rho(n);
factorize(d, fac);
factorize(n / d, fac);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
}
}
cin >> target_x >> target_y;
ull start = a[target_x][target_y];
if (start == 1) {
cout << 0 << '\n';
return 0;
}
vector<ull> fac;
factorize(start, fac);
sort(fac.begin(), fac.end());
fac.erase(unique(fac.begin(), fac.end()), fac.end());
int ans = 0;
for (int idx = 0; idx < (int) fac.size(); idx++) {
ull p = fac[idx];
int best = INT_MAX;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (a[i][j] % p != 0) {
int d = abs(i - target_x) + abs(j - target_y);
if (d < best) {
best = d;
}
}
}
}
if (best == INT_MAX) {
cout << -1 << '\n';
return 0;
}
ans = max(ans, best);
}
cout << ans << '\n';
return 0;
}复杂度
- 时间复杂度:
- 空间复杂度:
其中 k 是目标点初始值的不同质因子个数,实际非常小。
总结
这题最关键的不是 gcd 本身,而是把局部更新看成“曼哈顿半径不断扩大的菱形区域 gcd”。
一旦把这个传播范围看清楚,问题就从“动态模拟”变成了“每个质因子最早什么时候会被周围某个格子消掉”。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
