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

该博客介绍了如何使用二维哈希来快速查找矩阵中的重复子正方形。通过两次更新过程,计算矩阵的哈希值,并利用进制哈希思想。在给定的矩阵中,通过二分枚举边长并统计子正方形哈希值的出现次数,找到至少重复两次的子正方形的最小边长。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

令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]: - **基础知识**:数据结构与算法的基础应用,例如数组、链表的操作,排序算法的选择与实现。 - **网络知识**:TCP/IP 协议栈的理解,HTTP/HTTPS 的工作流程,DNS 解析过程等。 - **Linux 操作系统**:Shell 编程、进程管理、文件权限控制等内容。 - **数据库操作**:SQL 查询语句优化,索引设计,事务处理机制。 以下是几个常见的考察方向及其对应的示例问题: #### 数据结构与算法实例 ```python def find_missing_number(nums): n = len(nums) + 1 expected_sum = n * (n + 1) // 2 actual_sum = sum(nums) return expected_sum - actual_sum ``` 此函数用于在一个长度为 `n` 的列表中找到缺失的一个整数,假设该列表原本应包含从 1 到 `n` 的连续正整数序列[^1]。 #### Linux Shell 实现脚本 编写一个简单的 Bash 脚本来监控磁盘空间并发送警告邮件给管理员: ```bash #!/bin/bash THRESHOLD=90 CURRENT=$(df / | grep / | awk '{ print $5}' | sed 's/%//g') if [ "$CURRENT" -gt "$THRESHOLD" ]; then echo "Warning: Disk space is low." | mail -s "Disk Space Alert" admin@example.com fi ``` 这段代码展示了如何通过命令行工具检测根目录下的可用存储容量,并在超过设定阈值时触发警报通知[^3]。 #### SQL 查询技巧 对于大规模关系型数据库中的性能调优而言,合理创建复合索引可以显著提升查询效率。比如,在一张记录用户活动日志的大表里查找特定时间段内的活跃用户数量时,应该考虑对日期字段建立覆盖索引来减少全表扫描带来的开销。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值