AcWing156 矩阵 求大矩阵中小矩阵存在性问题 二维哈希 详解、推导

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 1p3+0p2+1p1+1p0,用 s = s ∗ p + s [ i ] s=s*p+s[i] s=sp+s[i],从小到大乘就可以了。
  讨论完了一维的情况,二维矩阵的Hash,比如一个 3*2的矩阵我们想要达到的效果是:

  表格中的数字的意思是矩阵中的数字*p的表格中数字的次方。

54
32
10

  
  然后把表格中矩阵中对应的数字*p的表格中数字的次方所得的积依次相加。求和。就用这个Hash来表示这个矩阵的Hash。

  那么要怎么在mn的矩阵中求解这个ab矩阵的Hash呢。如果一个一个求,差不多是一个4重循环,复杂度大约有 O ( n ∗ m ∗ a ∗ b ) O(n*m*a*b) O(nmab)。铁定超时。
  其实如果我们对二维矩阵先一行一行的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} Apb1+Bpb2+...Cp0

  先考虑求第一行的情况:拿3*2的矩阵来举例子。对于第一行,我们可以O(1)得到:

10

这只是第一行,然后我们要得到的次数表格是:

54
32
10

发现第一行的次数加了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次。那么得到的次数表格就是

76
54
32
10

第一行加了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=spb+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[ja],l,r)p[ab];

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值