矩阵哈希
引入
矩阵哈希,又称二维前缀哈希。联系之前学习过的 字符串哈希,不难得出矩阵哈希的实质:将每个不同的矩阵映射为不同的整数,期望 \(O(1)\) 地比较矩阵是否相同。
对于一个 \(n\) 行 \(m\) 列的矩阵 \(A\),定义它的矩阵 Hash 值为:
类似于二维前缀和的推导过程,我们得出以下两条矩阵哈希的核心结论:
- 点 \((i, j)\) 的矩阵前缀 Hash 值的计算公式为:

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

解释: 如上图,紫色的矩形右下角为 \(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\) 的奇偶分类讨论:
-
若 \(k\) 为奇数,则对称中心就是 \((i, j)\)。那么矩阵左上角 \((i-r+1,j-r+1)\),右下角 \((i+r-1,j+r-1)\)。
-
若 \(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;
}

浙公网安备 33010602011771号