G. GCD on a grid

onlyblues發表於2024-04-13

G. GCD on a grid

Not long ago, Egor learned about the Euclidean algorithm for finding the greatest common divisor of two numbers. The greatest common divisor of two numbers $a$ and $b$ is the largest number that divides both $a$ and $b$ without leaving a remainder. With this knowledge, Egor can solve a problem that he once couldn't.

Vasily has a grid with $n$ rows and $m$ columns, and the integer ${a_i}_j$ is located at the intersection of the $i$-th row and the $j$-th column. Egor wants to go from the top left corner (at the intersection of the first row and the first column) to the bottom right corner (at the intersection of the last row and the last column) and find the greatest common divisor of all the numbers along the path. He is only allowed to move down and to the right. Egor has written down several paths and obtained different GCD values. He became interested in finding the maximum possible GCD.

Unfortunately, Egor is tired of calculating GCDs, so he asks for your help in finding the maximum GCD of the integers along the path from the top left corner to the bottom right corner of the grid.

Input

The first line contains an integer $t$ ($1 \le t \le {10}^{4}$) — the number of test cases.

The first line of each test case contains two integers $n$ and $m$ ($1 \le n, m \le 100$) — the number of rows and columns of the grid.

Then, there are $n$ lines, where the $i$-th line contains $m$ integers $(1 \le a_{i,j} \le {10}^{6}$) — the integers written in the $i$-th row and the $j$-th column of the grid.

It is guaranteed that the sum of $n \cdot m$ does not exceed $2 \cdot {10}^{5}$ over all test cases.

Output

For each test case, output the maximum possible GCD along the path from the top left cell to the bottom right cell in a separate line.

Example

input

3
2 3
30 20 30
15 25 40
3 3
12 4 9
3 12 2
8 3 12
2 4
2 4 6 8
1 3 6 9

output

10
3
1

解題思路

  由於要從 $(0,0)$ 走到 $(n-1,m-1)$,因此 $(0,0) \to (n-1,m-1)$ 所有路徑的最大公約數只可能是 $g_{0,0}$ 和 $g_{n-1,m-1}$ 的公約數,即 $\gcd(g_{0,0}, g_{n-1,m-1})$ 的約數。為此我們先對 $\gcd(g_{0,0}, g_{n-1,m-1})$ 分解約數儲存到陣列 $p$ 中,並用 $p_k$ 來表示第 $k$ 個約數,雜湊表 $\text{mp}[x]$ 表示 $x$ 是第幾個約數。

  定義 $f(i,j,k)$ 表示是否存在 $(0,0) \to (i,j)$ 最大公約數為 $p_k$ 的路徑。那麼 $f(i,j,k)$ 可以轉移到的狀態有 $f(i+1, j, \text{mp}\left[ \gcd(g_{i+1,j}, \, p_k)\right])$ 和 $f(i, j+1, \text{mp}\left[ \gcd(g_{i,j+1}, \, p_k)\right])$。

  最後就是列舉 $k$ 判斷是否存在一個狀態 $f(n-1,m-1,k)$ 為 $\text{true}$ 即可。

  可以發現 $k$ 的大小就是一個數約數的個數。在 $1 \sim 10^6$ 中,一個數最多有 $240$ 個約數。另外當 $A$ 不是特別大時,約數個數的估計公式為 $\sqrt[3]{A}$。

  AC 程式碼如下,時間複雜度為 $O(nm\sqrt[3]{A} \log{A})$:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 105, M = 245, K = 1e6 + 5;

int g[N][N];
int p[M], mp[K], sz;
bool f[N][N][M];

void solve() {
    int n, m;
    cin >> n >> m;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> g[i][j];
        }
    }
    sz = 0;
    int d = __gcd(g[0][0], g[n - 1][m - 1]);
    for (int i = 1; i * i <= d; i++) {
        if (d % i == 0) {
            p[sz++] = i;
            if (d / i != i) p[sz++] = d / i;
        }
    }
    for (int i = 0; i < sz; i++) {
        mp[p[i]] = i;
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            for (int k = 0; k < sz; k++) {
                f[i][j][k] = false;
            }
        }
    }
    f[0][0][mp[d]] = true;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            for (int k = 0; k < sz; k++) {
                if (!f[i][j][k]) continue;
                if (i + 1 < n) f[i + 1][j][mp[__gcd(g[i + 1][j], p[k])]] = true;
                if (j + 1 < m) f[i][j + 1][mp[__gcd(g[i][j + 1], p[k])]] = true;
            }
        }
    }
    int ret = 0;
    for (int i = 0; i < sz; i++) {
        if (f[n - 1][m - 1][i]) ret = max(ret, p[i]);
    }
    cout << ret << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    
    return 0;
}

  再提供另外一個做法。

  列舉 $\gcd(g_{0,0}, g_{n-1,m-1})$ 的每一個約數 $d$,把滿足 $d \mid g_{i,j}$ 的位置 $(i,j)$ 標記出來,判斷能否只透過這些被標記的位置從 $(0,0)$ 走到 $(n-1,m-1)$,可以用 dp 實現,與上面的實現類似。

  AC 程式碼如下,時間複雜度為 $O(nm\sqrt[3]{A})$:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 105;

int n, m;
int g[N][N];
bool vis[N][N];
bool f[N][N];

bool check(int d) {
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (g[i][j] % d == 0) vis[i][j] = true;
            else vis[i][j] = false;
            f[i][j] = false;
        }
    }
    f[0][0] = true;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (!f[i][j]) continue;
            if (vis[i + 1][j]) f[i + 1][j] = true;
            if (vis[i][j + 1]) f[i][j + 1] = true;
        }
    }
    return f[n - 1][m - 1];
}

void solve() {
    cin >> n >> m;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> g[i][j];
        }
    }
    int d = __gcd(g[0][0], g[n - 1][m - 1]);
    int ret = 0;
    for (int i = 1; i * i <= d; i++) {
        if (d % i == 0) {
            if (check(i)) ret = max(ret, i);
            if (d / i != i && check(d / i)) ret = max(ret, d / i);
        }
    }
    cout << ret << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    
    return 0;
}

參考資料

  Codeforces Round 938 (Div. 3) Editorial:https://codeforces.com/blog/entry/128243