对称子矩形(DEBUG)题解

题目描述

在调试程序时,Mirko注意到程序中的一个错误可能与程序内存中所谓的"方杀手"的存在有关。程序内存是一个由R行和C列组成的矩阵,仅由零和一组成。方杀手是内存中的一个正方形子矩阵,由多个字符组成,当旋转180度时看起来完全相同。例如,以下矩阵包含3个方杀手:

101010

111001

101001

(1)

....10

....01

......

(2)

......

...00.

...00.

(3)

101...

111...

101...

Mirko想知道最大方杀手的大小与程序中的错误是否有关。请帮助Mirko编写一个程序,根据内存的布局输出最大方杀手的大小。方杀手的大小是方杀手包含的行数(或列数)。在上面的例子中,方杀手的大小分别为2、2和3。

样例

样例输入1

3 6

101010

111001

101001

样例输出1

3

样例输入2

4 5

10010

01010

10101

01001

样例输出2

3

样例输入3

3 3

101

111

100

样例输出3

-1

解法

暴力——人类的智慧!

我们可以枚举边长,然后枚举左上角,再判断是否为对称子矩形。
枚举边长: O ( n ) O(n) O(n)
枚举左上角: O ( n 2 ) O(n^2) O(n2)
判断是否为对称子矩形: O ( n 2 ) O(n^2) O(n2)
总时间复杂度: O ( n 5 ) O(n^5) O(n5)
恭喜你,40分!

优化的开始——二分!

我们可以想想,枚举边长的时候可不可以二分呢?那么,时间复杂度就完美的把一个 O ( n ) O(n) O(n) 降成 O ( l o g 2 n ) O(log_2^n) O(log2n)
枚举边长: O ( l o g 2 n ) O(log_2^n) O(log2n)
枚举左上角: O ( n 2 ) O(n^2) O(n2)
判断是否为对称子矩形: O ( n 2 ) O(n^2) O(n2)
总时间复杂度: O ( n 4 l o g 2 n ) O(n^4log_2^n) O(n4log2n)
虽然不知道有多少分~~(懒得测了)~~,但时间复杂度已经降下来了。

只判断边框——降了一个 O ( n ) O(n) O(n) !

我们在判断的时候会出现多余的地方,用一个 f l a g [ x ] [ y ] [ l e n ] flag[x][y][len] flag[x][y][len] 数组记录以 ( x , y ) (x,y) (x,y) 为左上角,边长为 l e n len len 的矩阵是否为回文串,每次判断的时候如果 f l a g [ x + 1 ] [ y + 1 ] [ l e n − 2 ] flag[x + 1][y + 1][len - 2] flag[x+1][y+1][len2] 为假,直接跳掉,每次判断的时候只要判断两条长和两条宽就行了。

20230828_095458

枚举边长: O ( n ) O(n) O(n) O ( l o g 2 n ) O(log_2^n) O(log2n)
枚举左上角: O ( n 2 ) O(n^2) O(n2)
判断是否为对称子矩形: O ( n ) O(n) O(n)
总时间复杂度: O ( n 4 ) O(n^4) O(n4) O ( n 3 l o g 2 n ) O(n^3log_2^n) O(n3log2n)
O ( n 3 l o g 2 n ) O(n^3log_2^n) O(n3log2n) 接近 O ( n 3 ) O(n^3) O(n3) 有100的希望了!

逆流而上——又优化了一点点!

我们可以倒着枚举边长,只要找到了,退出整个循环,有可能优化一点点,但效果不明显~~(卡常用)~~。

几个long long跑得快——二进制拆分!

我们可以每64个数为一组,判断的时候直接 O ( n l o g 64 n ) O(nlog_{64}^n) O(nlog64n) O ( l o g 64 n ) O(log_{64}^n) O(log64n) 直接可以忽略不计!
枚举边长: O ( n ) O(n) O(n) O ( l o g 2 n ) O(log_2^n) O(log2n)
枚举左上角: O ( n 2 ) O(n^2) O(n2)
判断是否为对称子矩形: O ( n l o g 64 n ) O(nlog_{64}^n) O(nlog64n)
总时间复杂度: O ( n 4 l o g 64 n ) O(n^4log_{64}^n) O(n4log64n) O ( n 3 l o g 2 n l o g 64 n ) O(n^3log_2^nlog_{64}^n) O(n3log2nlog64n)

一个矩阵一个数——二维hash!

枚举边长和左上角不能再优化了,但判断的时间复杂度可以降成 O ( 1 ) O(1) O(1) !二维hash上场!
我们用p1、p2表示两个质数(随便什么质数都可以,不同就行了),第i行j列的数为 a i j × p 1 i × p 2 j a_{ij} \times p1^i \times p2^j aij×p1i×p2j,然后矩阵的哈希值就是所有数的和。
H = ∑ a i j × p 1 i × p 2 j % m o d H = \sum a_{ij} \times p1^i \times p2^j \% mod H=aij×p1i×p2j%mod

/*
*/
#include<bits/stdc++.h>
#define rep(i,s1,s2,s3) for(i = s1;i <= s2;i += s3)
#define r(i,s1,s2,s3) for(i = s1;i >= s2;i -= s3)
#define ull unsigned long long
#define sort stable_sort
#define INF 0x7f7f7f7f
#define ll long long 
using namespace std;
ull p1[1010],p2[1010],h1[1010][1010],h2[1010][1010];
int n,m,ans,a[1010][1010];
int main(){
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin>>n>>m;
	int i,j,l;
	ull s1,s2;
	char ch;
	rep(i,1,n,1) rep(j,1,m,1) cin>>ch,a[i][j] = ch - '0';
	rep(i,1,n + m,1) p1[i] = 2 * p1[i - 1],p2[i] = 3 * p2[i - 1];
	rep(i,1,n,1) rep(i,1,m,1)
		h1[i][j] = h1[i - 1][j] + h1[i][j - 1] - h1[i - 1][j - 1] + 
					p1[i] * p2[j] * a[i][j];
	rep(i,1,n,1) rep(i,1,m,1)
		h2[i][j] = h2[i + 1][j] + h2[i][j + 1] - h2[i + 1][j + 1] + 
					p2[n - i + 1] * p2[m - j + 1] * a[i][j];
	rep(l,1,min(n,m),1) rep(i,1,n - l,1) rep(j,1,m - l,1){
		s1 = h1[i + l][j + l] - h1[i + l][j - 1] - h1[i - 1][j + l] + h1[i - 1][j - 1];
		s2 = h2[i + l][j + l] - h2[i + l][j + 1] - h2[i + 1][j + l] + h2[i + 1][j + 1];
		if(s1 == s2){
			ans = l;
			l++;
			i = 1,j = 1;
		}
	}
	if(ans > 1) cout<<ans;
	else cout<<-1; 
	return 0;
}

恭喜,18分。
让我们想想,设矩阵的左上角为 ( x , y ) (x,y) (x,y),我们算出来的矩阵的值会多出 p 1 x − 1 × p 2 y − 1 p1^{x - 1} \times p2^{y - 1} p1x1×p2y1 倍!怎么办?除掉?~~(我不会)~~只有乘。

交叉相乘法

小学知识:
s 1 p 1 x − 1 × p 2 y − 1 = s 2 p 1 n − x − l × p 2 n − y − l \frac{s1}{p1^{x - 1} \times p2^{y - 1}} = \frac{s2}{p1^{n - x - l} \times p2^{n - y - l}} p1x1×p2y1s1=p1nxl×p2nyls2
s 1 × p 1 n − x − l × p 2 n − y − l = s 2 × p 1 x − 1 × p 2 y − 1 s1 \times p1^{n - x - l} \times p2^{n - y - l} = s2 \times p1^{x - 1} \times p2^{y - 1} s1×p1nxl×p2nyl=s2×p1x1×p2y1
所以,代码来——

/*
*/
#include<bits/stdc++.h>
#define rep(i,s1,s2,s3) for(i = s1;i <= s2;i += s3)
#define r(i,s1,s2,s3) for(i = s1;i >= s2;i -= s3)
#define ull unsigned long long
#define sort stable_sort
#define INF 0x7f7f7f7f
#define ll long long 
using namespace std;
ull p1[1010],p2[1010],h1[1010][1010],h2[1010][1010];
int n,m,ans,a[1010][1010];
int main(){
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin>>n>>m;
	int i,j,l;
	ull s1,s2;
	char ch;
	rep(i,1,n,1) rep(j,1,m,1) cin>>ch,a[i][j] = ch - '0';
	p1[0] = p2[0] = 1;
	rep(i,1,max(n,m),1) p1[i] = 101 * p1[i - 1],p2[i] = 131 * p2[i - 1];
	rep(i,1,n,1) rep(j,1,m,1)
		h1[i][j] = h1[i - 1][j] + h1[i][j - 1] - h1[i - 1][j - 1] + 
					p1[i] * p2[j] * a[i][j];
	r(i,n,1,1) r(j,m,1,1)
		h2[i][j] = h2[i + 1][j] + h2[i][j + 1] - h2[i + 1][j + 1] + 
					p1[n - i + 1] * p2[m - j + 1] * a[i][j];
	rep(l,1,min(n,m),1) rep(i,1,n - l,1) rep(j,1,m - l,1){
		s1 = (h1[i + l][j + l] - h1[i + l][j - 1] - h1[i - 1][j + l] + h1[i - 1][j - 1]) * p1[n - i - l] * p2[m - j - l];
		s2 = (h2[i][j] - h2[i + l + 1][j] - h2[i][j + l + 1] + h2[i + l + 1][j + l + 1]) * p1[i - 1] * p2[j - 1];
		if(s1 == s2){
			ans = l + 1;
			i = n,j = m;
		}
	}
	if(ans > 1) cout<<ans;
	else cout<<-1; 
	return 0;
}

统一倍数法

既然我们要减小倍数,保证他们的倍数统一,那么我们不如将倍数扩大保证他们统一,我的方法就是把他扩大成 p 1 n × p 2 m p1^n \times p2^m p1n×p2m,这样他们的倍数就统一了,也可以正常比较了。

/*
*/
#include<bits/stdc++.h>
#define rep(i,s1,s2,s3) for(i = s1;i <= s2;i += s3)
#define r(i,s1,s2,s3) for(i = s1;i >= s2;i -= s3)
#define ull unsigned long long
#define sort stable_sort
#define INF 0x7f7f7f7f
#define ll long long 
using namespace std;
ull p1[1010],p2[1010],h1[1010][1010],h2[1010][1010];
int n,m,ans,a[1010][1010];
int main(){
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin>>n>>m;
	int i,j,l;
	ull s1,s2;
	char ch;
	rep(i,1,n,1) rep(j,1,m,1) cin>>ch,a[i][j] = ch - '0';
	p1[0] = p2[0] = 1;
	rep(i,1,max(n,m),1) p1[i] = 101 * p1[i - 1],p2[i] = 131 * p2[i - 1];//����ģ����unsigned long long��Ȼ����Ϳ����� 
	rep(i,1,n,1) rep(j,1,m,1)
		h1[i][j] = h1[i - 1][j] + h1[i][j - 1] - h1[i - 1][j - 1] + 
					p1[i] * p2[j] * a[i][j];
	r(i,n,1,1) r(j,m,1,1)
		h2[i][j] = h2[i + 1][j] + h2[i][j + 1] - h2[i + 1][j + 1] + 
					p1[n - i + 1] * p2[m - j + 1] * a[i][j];
	rep(l,1,min(n,m),1) rep(i,1,n - l,1) rep(j,1,m - l,1){
		s1 = (h1[i + l][j + l] - h1[i + l][j - 1] - h1[i - 1][j + l] + h1[i - 1][j - 1]) * p1[n - i] * p2[m - j];
		s2 = (h2[i][j] - h2[i + l + 1][j] - h2[i][j + l + 1] + h2[i + l + 1][j + l + 1]) * p1[i + l - 1] * p2[j + l - 1];
		if(s1 == s2){
			ans = l + 1;
			i = n,j = m;
		}
	}
	if(ans > 1) cout<<ans;
	else cout<<-1; 
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值