AcWing156 矩阵 二维哈希 详解
讲解
实际上就是把一维Hash推广到二维矩阵上的Hash。
我们知道一维的Hash过程即把一段字符串Hash为一个p进制的数。比如字符串
1011
1011
1011,那么Hash出来就是
1
∗
p
3
+
0
∗
p
2
+
1
∗
p
1
+
1
∗
p
0
1*p^3+0*p^2+1*p^1+1*p^0
1∗p3+0∗p2+1∗p1+1∗p0,用
s
=
s
∗
p
+
s
[
i
]
s=s*p+s[i]
s=s∗p+s[i],从小到大乘就可以了。
讨论完了一维的情况,二维矩阵的Hash,比如一个 3*2的矩阵我们想要达到的效果是:
表格中的数字的意思是矩阵中的数字*p的表格中数字的次方。
5 | 4 |
---|---|
3 | 2 |
1 | 0 |
然后把表格中矩阵中对应的数字*p的表格中数字的次方所得的积依次相加。求和。就用这个Hash来表示这个矩阵的Hash。
那么要怎么在mn的矩阵中求解这个ab矩阵的Hash呢。如果一个一个求,差不多是一个4重循环,复杂度大约有
O
(
n
∗
m
∗
a
∗
b
)
O(n*m*a*b)
O(n∗m∗a∗b)。铁定超时。
其实如果我们对二维矩阵先一行一行的Hash一下,如果要求一个某一行的b个连续列区间的Hash,那么我们可以O(1)得到。得到的那个某一行的b个连续列区间的Hash的p进制数的构成一定是
A
∗
p
b
−
1
+
B
∗
p
b
−
2
+
.
.
.
C
∗
p
0
A*p^{b-1}+B*p^{b-2}+...C*p^{0}
A∗pb−1+B∗pb−2+...C∗p0。
先考虑求第一行的情况:拿3*2的矩阵来举例子。对于第一行,我们可以O(1)得到:
1 | 0 |
---|
这只是第一行,然后我们要得到的次数表格是:
5 | 4 |
---|---|
3 | 2 |
1 | 0 |
发现第一行的次数加了4,第一行0次方的数从0次加到了4次,推广到a*b的矩阵,第一行都会加(a-1)*b 次。其实就是看第一行的0次的变化,其从0次加到(a-1)*b次。下往上看,相同列之间,每一行差了b,所以我们每次乘p的b次方,乘a-1次就可以了。转化成式子就是:
int s=0;
for(int j=1;j<=a;j++){
s=s*p[b]+GetHash(h[j],l,r);
}
/* GetHash(h[j],l,r) 表示得到j行,l列到r列的Hash值。对每一行预处理,这个Hash可以轻松得到
p[b] 表示p的b次方
因为s从0开始,要算一次。
*/
这可以算出第一行到第A行的Hash,现在来考虑得到从第二行到第A+1行的Hash。如果你继续循环,第一行会再乘以b次。那么得到的次数表格就是
7 | 6 |
---|---|
5 | 4 |
3 | 2 |
1 | 0 |
第一行加了6次,所以就把这个Hash和减去第一行b
列的Hash值乘以p[a*b]次。就是:
s-=GetHash(h[1],l,r)*p[a*b];
推广到j-a列:
s-=GetHash(h[j-a],l,r)*p[a*b];
所以就得到了总的求Hash的代码:
ull s=0;
for(int j=1;j<=m;j++){
s=s*p[b]+GetHash(h[j],l,r);
if(j>a){
s-=GetHash(h[j-a],l,r)*p[a*b];
}
}
最后:如果还是不能理解,可以先错误的想成“哈希的哈希”,然后再去考虑细节。
AC代码
#include <ctime>
#include <cstdlib>
#include<iostream>
#include<algorithm>
#include<math.h>
#include<cstdio>
#include<string>
#include<string.h>
#include<list>
#include<queue>
#include<sstream>
#include<vector>
#include <cassert> // assert
#include<set>
#include<map>
#include<deque>
#include<stack>
#include<unordered_set>
using namespace std;
#define debug(x) cout<<"###"<<x<<"###"<<endl;
const int INF=0x3f3f3f3f,mod=1e9,Maxn=1e6+5;
const double eps=1e-8;
typedef long long ll;
typedef unsigned long long ull;
ull p[Maxn],hs[Maxn];
const ull base = 131;
ull Hash[1003][1003];
int n,m,a,b;
void init(){
p[0] = 1;
for(int i=1;i<Maxn;i++){
p[i] = p[i-1]*base;
}
}
ull GetHash(ull *h,int l,int r ){
return h[r]-h[l-1]*p[r-l+1];
}
unordered_set<ull> st;
int main(){
init();
cin>>m>>n>>a>>b;
ull ch;
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
scanf("%1d",&ch);
Hash[i][j]=Hash[i][j-1]*base+(ull)ch;
//debug(ch);
}
}
//debug(h[1][1]);
for(int i=b;i<=n;i++){
int l=i-b+1,r=i;
//cout<<l<<" "<<r<<endl;
ull s=0;
for(int j=1;j<=m;j++){
s=s*p[b]+GetHash(Hash[j],l,r);
if(j>a){
s-=GetHash(Hash[j-a],l,r)*p[a*b];
}
if(j>=a){
st.insert(s);
}
}
}
int q;
cin>>q;
while(q--){
ull s=0;
for(int i=1;i<=a;i++){
for(int j=1;j<=b;j++){
scanf("%1d",&ch);
//debug(ch);
s=s*base+ch;
}
}
if(st.count(s)){
cout<<1<<endl;
}
else{
cout<<0<<endl;
}
}
return 0;
}
/*
2 2 2 2
11
00
1
11
00
*/
总结:求二维矩阵的Hash:
a*b小矩阵,在已经求到了大矩阵每一行的Hash的条件下。
-
l到r列,由a行到a+1行的Hash公式是 s = s ∗ p b + g e t ( h [ j ] , l , r ) ; s = s*p^b + get(h[j], l, r); s=s∗pb+get(h[j],l,r);
-
超出处理
s − = G e t H a s h ( h [ j − a ] , l , r ) ∗ p [ a ∗ b ] ; s-=GetHash(h[j-a],l,r)*p[a*b]; s−=GetHash(h[j−a],l,r)∗p[a∗b];