「字符串算法」第2章 Hash 和 Hash 表课堂过关 Hash A. 【例题1】字符串哈希 B. 【例题2】回文子串 C. 【例题3】对称正方形 D. 【例题4】单词背诵 E. 【例题5】子正方形

「字符串算法」第2章 Hash 和 Hash 表课堂过关

先贴上:对拍程序

  • 把任意字符串映射成唯一一个非负整数的算法
  • 产生冲突概率极小
  • 通过hash值可以实现快速查找与匹配
  • 常用unsigned long long自然溢出取代模运算
  • 多次哈希:采用不同(p),(mod)多算几次,只有当值全部相等是才认为两个字符串相等((p)是进制数,(mod)是模数)
  • (p)常取(131,13331),此时产生冲突概率较低

Hash模板

ul Hash(char *s) {
    //将单个字符(包含大小写字符,数字)映射为数字value,value∈[0,p]
	#define value(_) (ul)((_ >= '0' && _ <= '9' ? (_ - '0' + 26) : (_ >= 'a' && _ <= 'z' ? _ - 'a' : _ - 'A' + 26) ))
	
	int siz = strlen(s);
	ul key = 0;
	for(int i = 0 ; i <= siz ; i++) {
		key = key * 131ull + (ul)i * value(s[i]);//131ull即p,乘p相当于key在p进制下左移一位
	}
	return key;
}

Hash模板2-子串哈希值+双重哈希

以【例题2】回文子串 为例

//p,mod的取值
const int p = 131 , p2 = 97; 
const ul mod = 1000000007 , mod2 = 19260817;

预处理

	ha[0] = s[0] - 'a';
	rha[0] = res[0] - 'a';
	ha2[0] = s[0] - 'a';
	rha2[0] = res[0] - 'a';
	for(rr int i = 1 ; i < siz ; i++)
		ha[i] = (ha[i - 1]* p + s[i] - 'a') % mod,
		rha[i] = (rha[i - 1] * p + res[i] - 'a') % mod,//res是s的反转串,rha和rha2是res的两个预处理数组
		ha2[i] = (ha2[i - 1]* p2 + s[i] - 'a') % mod2,
		rha2[i] = (rha2[i - 1] * p2 + res[i] - 'a') % mod2;

获取子串哈希值

void check(int L , int R) {
	int L = i - mid + 1 , R = i + mid - 1;
	ul key1 , key2;
    //pw[i]是p的i次方,pw2[i]是p2的i次方
    //  为了防止出现负数,这里先加上mod  "pw[R - L + 1] * "相当于p进制下左移R-L+1为(可以参考二进制的位运算)
	key1 = (ha[R]			+ mod - pw[R - L + 1] * ((L == 0) ? 0 : ha[L - 1])						% mod) % mod,
    //反转后	L对应siz-L-1	R对应siz-R-1
	key2 = (rha[siz - L - 1] + mod - pw[R - L + 1] * ((siz - R - 2 < 0) ? 0 : rha[siz - R - 1 - 1]) 	% mod) % mod;
	if(key1 != key2)//哈希值匹配
		res = false;
	else {//对拍时出现了较高的错误率,因此采用了二次哈希
		key1 = (ha2[R]			 + mod2 - pw2[R - L + 1] * ((L == 0) ? 0 : ha2[L - 1])						% mod2) % mod2,
		key2 = (rha2[siz - L - 1] + mod2 - pw2[R - L + 1] * ((siz - R - 2 < 0) ? 0 : rha2[siz - R - 1 - 1]) 	% mod2) % mod2;
		res = (key1 == key2);
	}
	return res;
}

若不能理解,自己从(p)进制角度推一下即可

Hash模板3-二维Hash

摘自信息学奥赛一本通-高效进阶

一维Hash是把一个字符串或一个序列用一个整数表示,二维Hash是把一个矩阵用一个整数表示.

我们进行两次Hash,第一次,我们横着Hash:

for(int i = 1 ; i <= n ; i++)
 for(int j = 1 ; j <= m ; j++)
     hash[i][j] = hash[i][j - 1] * p1 + a[i][j]

此时(hash(i,j))表示第(i)行前(j)个数的Hash值,此时我们进行第二次Hash:

for(int i = 1 ; i <= n ; i++)
 for(int j = 1 ; j <= m ; j++)
     hash[i][j] += hash[i - 1][j] * p2;

若我们要查询左上角为((x,y)) 右下角为((x_1,y_1))的矩阵的Hash值就为:

[hash(x_1,y_1)-hash(x-1,y_1)cdot p_2^{x_1-x+1}-hash(x_1,y-1)cdot p_1^{y_1-y+1}+hash(x-1,y-1)cdot p_1^{y_1-y+1}cdot p_2^{x_1-x+1} ]

写成代码:

key = hs[x1][y1] + hs[x - 1][y - 1] * pw1[y1 - y + 1] * pw2[x1 - x + 1] - hs[x - 1][y1] * pw2[x1 - x + 1] - hs[x1][y - 1] * pw1[y1 - y + 1]

关于Hash自然溢出做减法运算的一个问题

做取模运算时,为了避免负数,常常会用以下语句:c=(mod+a-b)%mod,但是自然溢出的情况下,模数为(2^{64}),已经超出unsigned long long的范围,用上述方法自然会有问题.
其实在自然溢出的情况下,我们不用加上模数,也就是说若(a<b),则(a-b==(1ull<<64)-b+a)成立((a,b)均为unsigned long long类型),可以自己验证一下,具体原因跟运算方式有关,本人比较菜,说不清楚

这是我做到T3才意识到的,不然T2我也不会傻傻敲双哈希

关于回文串处理的一个问题

为了避免对奇偶性的讨论,在处理回文串问题时,我们常常把字符串"abcb"变成"?a?b?c?b?", '?'代表一种特殊字符,但是这个字符的ASCLL码需要尽量高一些,或者直接为它赋一个独特的(value),不然在Hash的时候出现负数的(value)会造成冲突概率大大上升


A. 【例题1】字符串哈希

题目

传送门

「字符串算法」第2章 Hash 和 Hash 表课堂过关
Hash
A. 【例题1】字符串哈希
B. 【例题2】回文子串
C. 【例题3】对称正方形
D. 【例题4】单词背诵
E. 【例题5】子正方形

代码

AC代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ul unsigned long long
using namespace std;

ul Hash(char *s) {
	#define value(_) (ul)((_ >= '0' && _ <= '9' ? (_ - '0' + 26) : (_ >= 'a' && _ <= 'z' ? _ - 'a' : _ - 'A' + 26) ))
	
	int siz = strlen(s);
	ul key = 0;
	for(int i = 0 ; i <= siz ; i++) {
		key = key * 63ull + (ul)i * value(s[i]);
	}
	return key;
}

int n;
char c[10010][1510];
ul key[10010];
int main() {
	int T;
	cin >> T;
	while(T--) {
		memset(c , 0 , sizeof(c));
		memset(key , 0 , sizeof(key));
		
		scanf("%d" , &n);
		for(int i = 1 ; i <= n ; i++)
			scanf("%s" , &c[i]);
			
		for(int i = 1 ; i <= n ; i++) {
			key[i] = Hash(c[i]);
		} 
		
		sort(key + 1 , key + n + 1);
		
		int cnt = 1;
		for(int i = 2 ; i <= n ; i++) {
			if(key[i] != key[i - 1])
				cnt++;
		}
		cout << cnt << endl;
	}
		
	return 0;
}

随机数据生成

可以自己测一下Hash的错误率

#include <bits/stdc++.h>
using namespace std;
int random(int r , int l = 1) {
	return  (unsigned long long) rand() * rand() * rand() % (r - l + 1) + l;
}
string st = "0123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM";
string dict[1010];
int main() {
	unsigned seed;
	cin >> seed;
	seed *= time(0);
	srand(seed);
	
	int T = 2;
	cout << T << endl;
	while(T--) {
		int n = 1000;
		for(int i = 1 ; i <= n ; i++) {
			int siz = 1000 + random(500 , -750);
			for(int j = 1 ; j <= siz ; j++)
				dict[i] += st[random(st.size() - 1 , 0)];
		}
		cout << n << endl;
		for(int i = 1 ; i <= n ; i++) {
			cout << dict[random(n)] << endl;
		}
	}
		
	return 0;
}

B. 【例题2】回文子串

题目

传送门

说明:自己出的数据,强度较大,仅供参考(数据强度:(5cdot 30cdot 10^6),5组数据,每组30个字符串,每个字符串最大长度为(10^6))

「字符串算法」第2章 Hash 和 Hash 表课堂过关
Hash
A. 【例题1】字符串哈希
B. 【例题2】回文子串
C. 【例题3】对称正方形
D. 【例题4】单词背诵
E. 【例题5】子正方形

小技巧

对于字符串"abacacbaaaab",我们可以将它变为"{a{b{a{c{a{c{b{a{a{a{a{b{"最后在对答案进行处理,以避免回文串长度奇偶性问题

代码

AC代码

**说明:一下代码本地跑很慢,但是不知道Ybt上为什么能AC,估计是自带O2 **

#include <iostream>
#include <cstdio>
#include <cstring>
#define nn 2000010
#define ul unsigned long long
#define rr register

//#pragma GCC optimize(2)

using namespace std;
char s[nn];
char tmp[nn];
char res[nn];
int siz;

const int p = 131 , p2 = 97; 
const ul mod = 1000000007 , mod2 = 19260817;
ul pw[nn];
ul pw2[nn];

ul ha[nn];
ul rha[nn];

ul ha2[nn];
ul rha2[nn];

int work() {
	memset(s , 0 , sizeof(s));
	memset(ha , 0 , sizeof(ha));
	memset(rha , 0 , sizeof(rha));
	siz = 0;
	
	char c_ = getchar();
	while(c_ < 'a' || c_ > 'z') {
		if(c_ == 'E')	return -1;
		c_ = getchar();
	}
	while(c_ >= 'a' && c_ <= 'z') {
		tmp[siz++] = c_;
		c_ = getchar();
	}
//	siz = strlen(tmp);
	s[0] = '{';
	
	for(rr int i = 0 ; i < siz ; ++i)
		s[(i << 1) + 1] = tmp[i] , s[(i << 1) + 2] = '{';
	siz = siz * 2 + 1;
	for(rr int i = 0 ; i < siz ; ++i)
		res[i] = s[siz - i - 1];
	//===为了减小常数,把上面函数里的东西全部放到这里了
	ha[0] = s[0] - 'a';
	rha[0] = res[0] - 'a';
	ha2[0] = s[0] - 'a';
	rha2[0] = res[0] - 'a';
	for(rr int i = 1 ; i < siz ; i++)
		ha[i] = (ha[i - 1]* p + s[i] - 'a') % mod,
		rha[i] = (rha[i - 1] * p + res[i] - 'a') % mod,
		ha2[i] = (ha2[i - 1]* p2 + s[i] - 'a') % mod2,
		rha2[i] = (rha2[i - 1] * p2 + res[i] - 'a') % mod2;
	//===
	int max_ = 1;
	for(rr int i = 0 ; i < siz ; ++i) {
		rr bool sig = false;
		rr int l = 1 , r = i + 1;
		if(r > siz - i)	r = siz - i;
		int mid;
		while(l < r) {
			mid = (l + r) >> 1;
			if(((l + r) & 1) != 0)	++mid;
			
			bool res;
			//===匹配
			int L = i - mid + 1 , R = i + mid - 1;
			ul key1 , key2;
			key1 = (ha[R]			 + mod - pw[R - L + 1] * ((L == 0) ? 0 : ha[L - 1])						% mod) % mod,
			key2 = (rha[siz - L - 1] + mod - pw[R - L + 1] * ((siz - R - 2 < 0) ? 0 : rha[siz - R - 1 - 1]) 	% mod) % mod;
			if(key1 != key2)
				res = false;
			else {
				key1 = (ha2[R]			 + mod2 - pw2[R - L + 1] * ((L == 0) ? 0 : ha2[L - 1])						% mod2) % mod2,
				key2 = (rha2[siz - L - 1] + mod2 - pw2[R - L + 1] * ((siz - R - 2 < 0) ? 0 : rha2[siz - R - 1 - 1]) 	% mod2) % mod2;
				res = (key1 == key2);
			}
			
			//===
			
			if(res)
				l = mid;
			else
				r = mid - 1;
			if(r <= max_) {sig = true ; break;}
		}
		if(sig)	continue;
		int tmp = l - 1;
			
		if(max_ < tmp)
			max_ = tmp;
	}
	return max_;
//*/
}
int main() {
	pw[0] = 1;
	for(int i = 1 ; i <= 1000010 ; i++)
		pw[i] = pw[i - 1] * p % mod;
	pw2[0] = 1;
	for(int i = 1 ; i <= 1000010 ; i++)
		pw2[i] = pw2[i - 1] * p2 % mod2;
		
	int ans , cnt = 0;
	while((ans = work()) != -1)
		printf("Case %d: %d
" , ++cnt , ans);
	return 0;
}

随机数据生成

#include <bits/stdc++.h>
using namespace std;
int random(int r , int l = 1) {
	return (long long) rand() * rand() * rand() % (r - l + 1) + l;
}
int main() {
	unsigned seed;
	cin >> seed;
	seed *= time(0);
	srand(seed);
	
	for(int i = 1 ; i <= 30 ; i++) {
			
		int siz = random(1e6);
		for(int j = 1 ; j <= siz ; j++)
			putchar(random('z' , 'a'));
		putchar('
');
	}
	puts("END");
		
	return 0;
}

C. 【例题3】对称正方形

题目

「字符串算法」第2章 Hash 和 Hash 表课堂过关
Hash
A. 【例题1】字符串哈希
B. 【例题2】回文子串
C. 【例题3】对称正方形
D. 【例题4】单词背诵
E. 【例题5】子正方形

思路

最简单的做法是枚举正方形左上角顶点的横纵坐标,正方形边长,时间复杂度(O(n^3)),用Hash进行(O(1))判断对称,期望得分(30)

优化一下,如果一个正方形合法,那么把该正方形四周一圈削掉也一样合法,因此,我们考虑枚举一个正方形的中心,二分其最大边长(l),使该正方形恰好满足对称(ans)直接加上(l)即可,时间复杂度(O(ncdot mlog n)),期望得分(100)

另外,对于边长为偶数的正方形,其中心不在数上,需要单独在做一个二分处理

一个我一开始忽略的小问题:
做哈希的时候,最少有三个矩阵(一般为原矩阵,水平翻转,竖直反转),我一开始只有两个矩阵(原矩阵,原矩阵水平翻转再后竖直翻转的矩阵),错因是像下面这样的矩阵会被判合法,导致输出答案大于标准答案

1 2
2 1

对于矩阵翻转后坐标变换问题,这里不再赘述,可以自己推一下,或参考下面可读性极低的代码

代码

AC代码

#include <iostream>
#include <cstdio>

#define nn 3010
#define ul unsigned long long
#define rr register
using namespace std;
ul read() {
	ul re = 0;
	char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0',
		c = getchar();
	return re;
}
const ul p1 = 1331 , p2 = 13331;
int n , m;
int mat[nn][nn];
int res[nn][nn];
int rres[nn][nn];

ul hs[nn][nn];
ul rhs[nn][nn];
ul rrhs[nn][nn];

ul pw1[nn] , pw2[nn];

inline bool check(int i , int j , int k) {//判断以(i,j)为左上角顶点,边长为k+1的矩阵是否合法

	int x = i , y = j , x1 = i + k , y1 = j + k;
	ul key1 = hs[x1][y1] + hs[x - 1][y - 1] * pw1[y1 - y + 1] * pw2[x1 - x + 1] - hs[x - 1][y1] * pw2[x1 - x + 1] - hs[x1][y - 1] * pw1[y1 - y + 1];

	x = n - x1 + 1 , y = m - y1 + 1 , x1 = n - i + 1 , y1 = m - j + 1;
	ul key2 = rhs[x1][y1] + rhs[x - 1][y - 1] * pw1[y1 - y + 1] * pw2[x1 - x + 1] - rhs[x - 1][y1] * pw2[x1 - x + 1] - rhs[x1][y - 1] * pw1[y1 - y + 1];
	if(key1 != key2)	return false;
	int t;
	x = i , y = j , x1 = i + k , y1 = j + k;
	x = n - x + 1 , x1 = n - x1 + 1;
	t = x ,	x = x1 , x1 = t;
	ul key3 = rrhs[x1][y1] + rrhs[x - 1][y - 1] * pw1[y1 - y + 1] * pw2[x1 - x + 1] - rrhs[x - 1][y1] * pw2[x1 - x + 1] - rrhs[x1][y - 1] * pw1[y1 - y + 1];
	return key1 == key3;
}
#define min_(_ , __) ((_) < (__) ? (_) : (__))
int main() {
	pw1[0] = pw2[0] = 1;
	for(int i = 1 ; i <= 3000 ; i++)
		pw1[i] = pw1[i - 1] * p1,
		         pw2[i] = pw2[i - 1] * p2;

	n = read();
	m = read();
	for(int i = 1 ; i <= n ; i++)
		for(int j = 1 ; j <= m ; j++)
			mat[i][j] = res[n - i + 1][m - j + 1] = rres[n - i + 1][j] = read();

	for(int i = 1 ; i <= n ; i++)
		for(int j = 1 ; j <= m ; j++)
			hs[i][j] = hs[i][j - 1] * p1 + mat[i][j],
			rhs[i][j] = rhs[i][j - 1] * p1 + res[i][j],
			rrhs[i][j] = rrhs[i][j - 1] * p1 + rres[i][j];

	for(int i = 1 ; i <= n ; i++)
		for(int j = 1 ; j <= m ; j++)
			hs[i][j] += hs[i - 1][j] * p2,
			rhs[i][j] += rhs[i - 1][j] * p2,
			rrhs[i][j] += rrhs[i - 1][j] * p2;

/*暴力
	int ans = 0;
	for(rr int i = 1 ; i <= n ; ++i)
		for(rr int j = 1 ; j <= m ; ++j) {
			for(rr int k = 0 ; k + i <= n && k + j <= m ; ++k) {
				if(check(i , j , k)) {
//					printf("%d %d %d
" , i , j , k);
					++ans;
				}
			}
		}/*/
	int ans = 0;
	for(int i = 1 ; i <= n ; i++)//边长为奇数
		for(int j = 1 ; j <= m ; j++) {
			int l = 1 , r = min_(min_(i , j) , min_(n - i + 1 , m - j + 1));
			while(l < r) {
				int mid = (l + r) / 2;
				if((l + r) % 2 == 1)	++mid;
				if(check(i - mid + 1 , j - mid + 1 , mid * 2 - 2))
					l = mid;
				else
					r = mid - 1;
			}
			ans += l;
		}
	for(int i = 1 ; i <= n ; i++)//边长为偶数
		for(int j = 1 ; j <= m ; j++) {
			int l = 0 , r = min_(min_(i , j) , min_(n - i , m - j ));
			while(l < r) {
				int mid = (l + r) / 2;
				if((l + r) % 2 == 1)	++mid;
				if(check(i - mid + 1 , j - mid + 1 , mid * 2 - 1))
					l = mid;
				else
					r = mid - 1;
			}
			ans += l;
		}
	/*
	puts("

");
	for(int i = 1 ; i <= n ; i++) {
		for(int j = 1 ; j <= m ; j++)
			cout << rres[i][j] <<' ';
		cout << endl;
	}*/
	cout << ans;
	return 0;
}

随机数据生成

#include <bits/stdc++.h>
using namespace std;
int random(int r , int l = 1) {
	return (long long)rand() * rand() * rand() % (r - l + 1) + l;
}
int main() {
	unsigned seed;
	cin >> seed;
	seed *= time(0);
	srand(seed);
	
	int n = random(1000) , m = random(1000);
	printf("%d %d
" , n , m);
	for(int i = 1 ; i <= n ; i++) {
		for(int j = 1 ; j <= m ; j++)
			printf("%d " , random(10));
		putchar('
');
	}
	return 0;
}

D. 【例题4】单词背诵

题目

「字符串算法」第2章 Hash 和 Hash 表课堂过关
Hash
A. 【例题1】字符串哈希
B. 【例题2】回文子串
C. 【例题3】对称正方形
D. 【例题4】单词背诵
E. 【例题5】子正方形

思路

个人觉得这题和Hash关系不大

直接把输进来的单词排序,编号,用二分法把文本换成(1)~(n) 的整数,直接统计即可解决第一问(最多包含要背的单词数,设为(ans1))

对于第二问,我们设置指针(i)for(int i = 1 ; i <= m ; i++),和一个数组(dat_j)记录当(1)~(i)下标中编号为(j)的单词最晚出现时间
若当前单词个数达到(ans1),我们进行第二问的答案统计:设(tmp_i=i-min (dat_j)+1(1leq jleq n)),第二问的答案即为(tmp)中的最小值
(dat)数组涉及到单点修改和全段查询,用一个改装线段树维护即可,具体实现请看代码

总复杂度(O(nlog n))

代码

#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <algorithm>
#define nn 1010
using namespace std;
string sread() {
	string s;
	char c = getchar();
	while(c < 'a' || c > 'z')c = getchar();
	while(c >= 'a' && c <= 'z')
		s = s + c , c = getchar();
	return s;
}
int n , m;
string s[nn];

int txt[100010];
int vis[100010];

int root;
int lb[400010] , rb[400010];
int ls[400010] , rs[400010];
int dat[400010];
int build(int l , int r) {//线段树
	static int cnt = 1;
	int c = cnt++;
	lb[c] = l , rb[c] = r , dat[c] = (1 << 29);
	if(l == r)	return c;
	int mid = (l + r) / 2;
	ls[c] = build(l , mid);
	rs[c] = build(mid + 1 , r);
	return c;
}
#define min_(_ , __) ((_) < (__) ? (_) : (__))
void change(int p , int id , int d) {
	if(lb[p] == rb[p]) {
		dat[p] = d;
		return;
	}
	int mid = (lb[p] + rb[p]) / 2;
	if(id <= mid)	change(ls[p] , id , d);
	else			change(rs[p] , id , d);
	dat[p] = min_(dat[ls[p]] , dat[ rs[p]]);
}//线段树END
int main() {
	cin >> n;
	for(int i = 1 ; i <= n ; i++)
		s[i] = sread();
	sort(s + 1 , s + n + 1);
	
	cin >> m;
	for(int i = 1 ; i <= m ; i++) {
		string tmp = sread();
		int l = 1 , r = n;
		int mid;
		while(l <= r) {
			mid = (l + r) / 2;
			if(s[mid] == tmp) {
				txt[i] = mid;
				break;
			}
			else if(s[mid] < tmp)
				l = mid + 1;
			else
				r = mid - 1;
		}
	}
	
//	for(int i = 1 ; i <= m ; i++)
//		cout << txt[i] << '	';
	int cnt = 0;
	for(int i = 1 ; i <= m ; i++) {
		if(!vis[txt[i]])
			++cnt;
		vis[txt[i]] = true;
	}
	cout << cnt << endl;
	memset(vis , 0 , sizeof(vis));
	
	root = build(1 , n);
	int cnt2 = 0;
	int minn = 0;
	int ans = m;
	for(int i = 1 ; i <= m ; i++) {
		if(vis[txt[i]] == 0)
			++cnt2 , vis[txt[i]] = true;
		change(root , txt[i] , i);
		if(cnt2 == cnt) {
			if(ans > i - dat[root] + 1)
				ans = i - dat[root] + 1;
		}
	}
	cout << ans;
	return 0;
}

E. 【例题5】子正方形

题目

传送门

「字符串算法」第2章 Hash 和 Hash 表课堂过关
Hash
A. 【例题1】字符串哈希
B. 【例题2】回文子串
C. 【例题3】对称正方形
D. 【例题4】单词背诵
E. 【例题5】子正方形

思路

比较简单的一道题,枚举两个矩阵的左上角,二分其边长,用Hash(O(1))判断相等即可,总复杂度(O(n^4log n)),可通过

代码

AC代码

#include <iostream>
#include <cstdio>
#define ul unsigned long long
using namespace std;
int n;
int a[2][110][110];
ul hs[2][110][110];
const ul p1 = 1331 , p2 = 13331;
ul pw1[110] , pw2[110];

inline ul getkey(int k , int x , int y , int x1 , int y1) {
	return hs[k][x1][y1] - hs[k][x1][y - 1] * pw1[y1 - y + 1] - hs[k][x - 1][y1] * pw2[x1 - x + 1] + hs[k][x - 1][y - 1] * pw1[y1 - y + 1] * pw2[x1 - x + 1];
}
bool check(int len , ul key) {
	if(len == 0)	return true;
	int m = n - len + 1;
	for(int i = 1 ; i <= m ; i++)
		for(int j = 1 ; j <= m ; j++)
			if(key == getkey(1 , i , j , i + len - 1 , j + len - 1))
				return true;
	return false;
}
int read() {
	int re = 0;
	char c = getchar();
	while(c < '0' || c > '9')
		c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0',
		c = getchar();
	return re;
}
int main() {
	pw1[0] = pw2[0] = 1;
	for(int i = 1 ; i <= 60 ; i++)
		pw1[i] = pw1[i - 1] * p1 , pw2[i] = pw2[i - 1] * p2;
	
	n = read();
	for(int k = 0 ; k <= 1 ; k++)
		for(int i = 1 ; i <= n ; i++)
			for(int j = 1 ; j <= n ; j++)
				a[k][i][j] = read();
	for(int k = 0 ; k <= 1 ; k++) {
		for(int i = 1 ; i <= n ; i++)
			for(int j = 1 ; j <= n ; j++)
				hs[k][i][j] = hs[k][i][j - 1] * p1 + a[k][i][j];
		for(int i = 1 ; i <= n ; i++)
			for(int j = 1 ; j <= n ; j++)
				hs[k][i][j] += hs[k][i - 1][j] * p2;
	}
	
	int ans = 0;
	for(int i = 1 ; i <= n ; i++)
		for(int j = 1 ; j <= n ; j++) {
			int l = 0 , r = n - i + 1;
			if(r > n - j + 1)
				r = n - j + 1;
			int mid;
				
			while(l < r) {
				mid = (l + r) / 2;
				if((l + r) & 1)
					++mid;
				if(check(mid ,
				 getkey(0 , i , j , i + mid - 1 , j + mid - 1)))
					l = mid;
				else
					r = mid - 1;
			}
			if(l > ans)
				ans = l;
		}
	cout << ans;
	return 0;
}

随机数据生成

#include <bits/stdc++.h>
using namespace std;
int random(int r , int l =1 ) {
	return (long long)rand() * rand() * rand() % (r - l + 1) + l;
}
int main() {
	unsigned seed;
	cin >> seed;
	seed *= time(0);
	srand(seed);
	
	int n = random(50);
	cout << n << '
';
	for(int k = 0 ; k <= 1 ; k++)
		for(int i = 1 ; i <= n ; i++) {
			for(int j = 1 ; j <= n ; j++)
				printf("%d " , random(10 , 0));
			putchar('
');
		}
		
	
}