矩阵哈希

引入

矩阵哈希,又称二维前缀哈希。联系之前学习过的 字符串哈希,不难得出矩阵哈希的实质:将每个不同的矩阵映射为不同的整数,期望 \(O(1)\) 地比较矩阵是否相同。

对于一个 \(n\)\(m\) 列的矩阵 \(A\),定义它的矩阵 Hash 值为:

\[\sum_{i=1}^{n}\sum_{j=1}^{m}A_{i,j} \ p_x^{n-i}p_y^{m-j} \]

类似于二维前缀和的推导过程,我们得出以下两条矩阵哈希的核心结论:

  1. \((i, j)\) 的矩阵前缀 Hash 值的计算公式为:

\[H(i,j) = H(i-1,j)p_x + H(i,j-1)p_y - H(i-1,j-1)p_x p_y + a_{i,j} \]

矩阵哈希1

解释: 如上图,蓝色的矩形右下角为 \(H(i,j-1)\),绿色的矩形右下角为 \(H(i,j-1)\),它们重合部分的紫色矩形右下角为 \(H(i-1,j-1)\)。根据容斥原理,紫色矩形被减了两次。

  1. 一个 \((l_x, l_y)\)\((r_x, r_y)\) 的子矩阵的 Hash 值为:

\[H(r_x,r_y)-H(l_x-1,r_y)p_x^{r_x-l_x+1}-H(r_x,l_y-1)p_y^{r_y-l_y+1}+H(r_x-1)(r_y-1)p_x^{r_x-l_x+1}p_y^{r_y-l_y+1} \]

矩阵哈希2

解释: 如上图,紫色的矩形右下角为 \(H(r_x,l_y-1)\),绿色的矩形右下角为 \(H(l_x-1,r_y)\),它们重合部分的黄色矩形右下角为 \(H(l_x-1,l_y-1)\)。黑框矩形右下角为 \(H(r_x-1,r_y-1)\)。根据容斥原理,黄色矩形被减了两次。依然是容斥原理。

其中,\(p_x, p_y\) 是两个不相等却相近的质数。如果手动取模 \(mod\) 要保证取同一个值。

例题

Matrix Matcher

问题:\(T\) 组数据,每组数据给定一个 \(N\times M\)\(A\) 矩阵和一个 \(X\times Y\)\(B\) 矩阵求 \(B\) 矩阵在 \(A\) 矩阵中出现的次数。

#include <iostream>
using namespace std;
typedef unsigned long long ull;
const int N = 1e3 + 8, px = 131, py = 137;
int n, m, x, y, ans, T;
ull pxpow[N], pypow[N], ahs[N][N], bhs[N][N];
ull geths(ull h[][N], int lx, int ly, int rx, int ry) {
    return h[rx][ry] - h[lx - 1][ry] * pxpow[rx - lx + 1] - h[rx][ly - 1] * pypow[ry - ly + 1]
    + h[lx - 1][ly - 1] * pxpow[rx - lx + 1] * pypow[ry - ly + 1];
}
int main() {
    pxpow[0] = pypow[0] = 1;
    for (int i = 1; i < N; i++) pxpow[i] = pxpow[i - 1] * px, pypow[i] = pypow[i - 1] * py;
    cin >> T;
    while (T--) {
        cin >> n >> m;
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++) {
                char c; cin >> c;
                ahs[i][j] = ahs[i - 1][j] * px + ahs[i][j - 1] * py - ahs[i - 1][j - 1] * px * py + c;
            }
        cin >> x >> y;
        for (int i = 1; i <= x; i++)
            for (int j = 1; j <= y; j++) {
                char c; cin >> c;
                bhs[i][j] = bhs[i - 1][j] * px + bhs[i][j - 1] * py - bhs[i - 1][j - 1] * px * py + c;
            }
        ans = 0;
        for (int i = 1; i <= n - x + 1; i++)
            for (int j = 1; j <= m - y + 1; j++)
                if (bhs[x][y] == geths(ahs, i, j, i + x - 1, j + y - 1)) ans++;
        cout << ans << '\n';
    }
    return 0;
}

对称的正方形

问题:给定一个矩阵,求该矩阵中上下对称且左右对称的正方形子矩阵的个数。

根据矩阵对称性的定义,发现满足单调性,考虑枚举矩阵对称中心,二分答案矩阵半径。设 \(k \times k\) 的子矩阵中一点 \((\lceil \frac{k}{2} \rceil,\lceil \frac{k}{2} \rceil)\)\((i,j)\),矩阵半径为 \(r\),再对 \(k\) 的奇偶分类讨论:

  1. \(k\) 为奇数,则对称中心就是 \((i, j)\)。那么矩阵左上角 \((i-r+1,j-r+1)\),右下角 \((i+r-1,j+r-1)\)

  2. \(k\) 为偶数,可以认为对称中心是 \((i + 0.5,j+0.5)\)。那么矩阵左上角 \((i-r+1,j-r+1)\),右下角 \((i+r,j+r)\)

正常、上下翻转、左右翻转存三个矩阵的 Hash 值比较即可。

#include <iostream>
#include <algorithm>
using namespace std;
typedef unsigned long long ull;
const int N = 1e3 + 8, px = 1e9 + 7, py = 1e9 + 9;
int n, m, a[N][N];
ull pxpow[N], pypow[N], hs[3][N][N];
ull geths(ull h[][N], int lx, int ly, int rx, int ry) {
    return h[rx][ry] - h[lx - 1][ry] * pxpow[rx - lx + 1] - h[rx][ly - 1] * pypow[ry - ly + 1]
    + h[lx - 1][ly - 1] * pxpow[rx - lx + 1] * pypow[ry - ly + 1];
}
bool check(int lx, int ly, int rx, int ry) {
    return geths(hs[0], lx, ly, rx, ry) == geths(hs[1], n - rx + 1, ly, n - lx + 1, ry)
    && geths(hs[0], lx, ly, rx, ry) == geths(hs[2], lx, m - ry + 1, rx, m - ly + 1);
}
int main() {
    pxpow[0] = pypow[0] = 1;
    for (int i = 1; i < N; i++) pxpow[i] = pxpow[i - 1] * px, pypow[i] = pypow[i - 1] * py;
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) cin >> a[i][j];
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            hs[0][i][j] = hs[0][i - 1][j] * px + hs[0][i][j - 1] * py - hs[0][i - 1][j - 1] * px * py + a[i][j];
            hs[1][i][j] = hs[1][i - 1][j] * px + hs[1][i][j - 1] * py - hs[1][i - 1][j - 1] * px * py + a[n - i + 1][j];
            hs[2][i][j] = hs[2][i - 1][j] * px + hs[2][i][j - 1] * py - hs[2][i - 1][j - 1] * px * py + a[i][m - j + 1];
        }
    int ans = 0;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            int l = 0, r = min({i, j, n - i + 1, m - j + 1}), mid;
            while (l < r) {
                mid = l + r + 1 >> 1;
                if (check(i - mid + 1, j - mid + 1, i + mid - 1, j + mid - 1)) l = mid;
                else r = mid - 1;
            }
            ans += l;
            l = 0, r = min({i, j, n - i, m - j});
            while (l < r) {
                mid = l + r + 1 >> 1;
                if (check(i - mid + 1, j - mid + 1, i + mid, j + mid)) l = mid;
                else r = mid - 1;
            }
            ans += l;
        }
    cout << ans;
    return 0;
}

Blue Mary 的战役地图

问题:求两个 \(n \times n\) 的矩阵的最大公共正方形矩阵的边长。

\(O(n^3)\) 地枚举 \(A\) 矩阵中的所有子矩阵并存入哈希表,再 \(O(n^3)\) 地枚举 \(B\) 矩阵中的所有子矩阵并判断 Hash 值是否相等即可。

实现时可以选择手打哈希表或 STL 库提供的 unordered_set

#include <iostream>
#include <unordered_set>
using namespace std;
typedef unsigned long long ull;
const int N = 58, px = 1e9 + 7, py = 1e9 + 9;
ull ahs[N][N], bhs[N][N], pxpow[N], pypow[N];
ull geths(ull h[][N], int lx, int ly, int rx, int ry) {
    return h[rx][ry] - h[lx - 1][ry] * pxpow[rx - lx + 1] - h[rx][ly - 1] * pypow[ry - ly + 1] + h[lx - 1][ly - 1] * pxpow[rx - lx + 1] * pypow[ry - ly + 1];
}
unordered_set<ull> ht;
int n;
int main() {
    pxpow[0] = pypow[0] = 1;
    for (int i = 1; i < N; i++) pxpow[i] = pxpow[i - 1] * px, pypow[i] = pypow[i - 1] * py;
    cin >> n;
    for (int i = 1; i <= n; i++)
        for (int j = 1, x; j <= n; j++) {
            cin >> x;
            ahs[i][j] = ahs[i - 1][j] * px + ahs[i][j - 1] * py - ahs[i - 1][j - 1] * px * py + x;
        }
    for (int i = 1; i <= n; i++) 
        for (int j = 1, x; j <= n; j++) {
            cin >> x;
            bhs[i][j] = bhs[i - 1][j] * px + bhs[i][j - 1] * py - bhs[i - 1][j - 1] * px * py + x;
        }
    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n - k + 1; i++)
            for (int j = 1; j <= n - k + 1; j++)
                ht.insert(geths(ahs, i, j, i + k - 1, j + k - 1));
    for (int k = n; k >= 1; k--)
        for (int i = 1; i <= n - k + 1; i++)
            for (int j = 1; j <= n - k + 1; j++)
                if (ht.count(geths(bhs, i, j, i + k - 1, j + k - 1)))
                    return cout << k, 0;
}
posted @ 2026-02-10 22:10  zheyutao  阅读(31)  评论(0)    收藏  举报