牛客NC13610 矩阵 - 二维(矩阵)哈希 + 二分

一维哈希可以将字符串映射为一个整数,而二维哈希则可以将矩阵映射为一个整数。

令h[i][j]表示左上顶点为(1, 1),右下顶点为(i, j)的矩阵的哈希值,mat[i][j]表示原矩阵。

采用如下的方式更新h[i][j]:

for(int i = 1; i <= n; ++i)
    for(int j = 1; j <= m; ++j)
        h[i][j] = h[i][j - 1] * base1 + mat[i][j];
for(int i = 1; i <= n; ++i)
    for(int j = 1; j <= m; ++j)
        h[i][j] = h[i - 1][j] * base2 + h[i][j];

其实第一次更新时h[i][j]是表示mat[i][1] ~ mat[i][j]子串的哈希值,到了第二次更新h[i][j]才表示上面所定义的意义。二维哈希与一维哈希都采用了进制哈希的思想,因此应该不难理解。

二维哈希获取子矩阵的哈希值的方式如下:

/**
 * (i, j)表示矩阵的右下顶点
 * leni表示矩阵沿着i所在坐标轴的边长
 * lenj表示矩阵沿着j所在坐标轴的边长
*/
unsigned long long get_submat_hash(int i, int j, int leni, int lenj){
    // p1[i]表示base1的i次方, p2[i]表示base2的i次方
    return h[i][j] - h[i - leni][j] * p2[leni] - h[i][j - lenj] * p1[lenj] + h[i - leni][j - lenj] * p1[leni] * p2[lenj];
}

在本题中,在计算出原矩阵的二维哈希值之后,我们可以二分枚举正方形的边长k,然后枚举所有边长为k的正方形的哈希值,并用map记录其出现次数,如果某一个哈希值出现了两次,则代表存在边长为k的正方形出现了至少两次,更新答案并增大k,否则减小k。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#define _min(x, y) x < y ? x : y
#define _max(x, y) x > y ? x : y
using namespace std;
typedef unsigned long long ull;
const int maxn = 505;
const ull basen = 233333;
const ull basem = 13331;
char mat[maxn][maxn];
ull h[maxn][maxn], pn[maxn], pm[maxn];
map<ull, int> mp;

void hash(int n, int m){
	h[0][0] = 0;
	pn[0] = pm[0] = 1;
	for(int i = 1; i <= m; ++i){
		h[0][i] = 0;
		pm[i] = pm[i - 1] * basem;
	}
	for(int i = 1; i <= n; ++i){
		h[i][0] = 0;
		pn[i] = pn[i - 1] * basen;
	}
	
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= m; ++j)
			h[i][j] = h[i][j - 1] * basem + (ull)mat[i][j];
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= m; ++j)
			h[i][j] = h[i - 1][j] * basen + h[i][j];
}

ull get_submat_hash(int x, int y, int len){
	return h[x][y] - h[x - len][y] * pn[len] - h[x][y - len] * pm[len] + h[x - len][y - len] * pn[len] * pm[len];
}

bool check(int len, int n, int m){
	mp.clear();
	for(int i = len; i <= n; ++i){
		for(int j = len; j <= m; ++j){
			ull _hash = get_submat_hash(i, j, len);
			mp[_hash]++;
			if(mp[_hash] >= 2)
				return true;
		}
	}
	return false;
}

int main(){
	int n, m;
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; ++i)
		scanf("%s", mat[i] + 1);
	hash(n, m);
	int ans = 0;
	int l = 1, r = min(n, m) + 1;
	while(l < r){
		int mid = (l + r) >> 1;
		if(check(mid, n, m)){
			l = mid + 1;
			ans = mid;
		}
		else
			r = mid;
	}
	printf("%d\n", ans);
	return 0;
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值