蓝书(算法竞赛进阶指南)刷题记录——BZOJ2351 Matrix(hash)

题目:BZOJ2351.
题目大意:给定一个 N ∗ M N*M NM的大 01 01 01矩阵,以及 Q Q Q A B AB AB的小 01 01 01矩阵,问有每个小矩阵是否在大矩阵中出现.
1 ≤ n , m ≤ 1 0 3 , q = 1 0 3 1\leq n,m\leq 10^3,q=10^3 1n,m103,q=103,小矩阵为边长 ≤ 100 \leq 100 100的正方形.

看到这道题我们就会想到考虑这个问题在一维情况下的做法,直接什么AC自动机,什么KMP之类的算法都可以解决.

但是考虑在二维情况下怎么办?二维AC自动机 矩阵hash!

我们考虑字符串hash是把一个字符串看成一个 P P P进制数,那么矩阵hash呢?考虑先把矩阵的每一行看成一个 P 1 P1 P1进制数得到所有数,再把每一行所表示的 P P P进制数看成一个数,把整个矩阵看成一个字符串,那么再把这个字符串当成一个 P 2 P2 P2进制数就可以了.

具体来说,我们设置一个数 P 1 P1 P1,先把每一行当成一个字符串预处理出每个前缀对应的 P 1 P1 P1进制数.然后对于每一个 i i i j j j列的前缀矩阵,我们只要把每一行 j j j个数的前缀对应的 P 1 P1 P1进制数当成这个子矩阵对应的一个字符串,然后把这个字符串当成一个 P 2 P2 P2进制数预处理一下每个前缀对应的 P 2 P2 P2进制数即可.

那么在这个情况下,我们类比二维前缀和,设 i i i j j j列的前缀矩阵对应的值为 s [ 1.. i ] [ 1.. j ] s[1..i][1..j] s[1..i][1..j],第 x 1 + 1 x1+1 x1+1行到第 x 2 x2 x2行,第 y 1 + 1 y1+1 y1+1列到 y 2 y2 y2列所对应的数值为 c ( x 1 , x 2 , y 1 , y 2 ) c(x1,x2,y1,y2) c(x1,x2,y1,y2),那么:
c ( x 1 , x 2 , y 1 , y 2 ) = s [ x 2 ] [ y 2 ] − P 2 y 2 − y 1 s [ x 2 ] [ y 1 ] − P 1 x 2 − x 1 s [ x 1 ] [ y 2 ] + P 2 y 2 − y 1 P 1 x 2 − x 1 s [ x 1 ] [ y 1 ] c(x1,x2,y1,y2)=s[x2][y2]-P2^{y2-y1}s[x2][y1]-P1^{x2-x1}s[x1][y2] +P2^{y2-y1}P1^{x2-x1}s[x1][y1] c(x1,x2,y1,y2)=s[x2][y2]P2y2y1s[x2][y1]P1x2x1s[x1][y2]+P2y2y1P1x2x1s[x1][y1]

这个大概还好理解吧…我知道写得很丑

反正理解了这个式子后,我们就可以直接写出代码了(注:这道题有一道相同的题BZOJ2462,但是这份代码在那道题上会TLE,大概是被卡常或者卡hash了):

#include<bits/stdc++.h>
  using namespace std;
 
#define Abigail inline void
typedef long long LL;
typedef unsigned long long ULL;
 
const int N=1000;
const ULL P1=131,P2=13331,M=5000081;
 
char rc(){
  char c=getchar();
  while (c<'0'||c>'1') c=getchar();
  return c;
}
 
ULL s[N+9][N+9],Pow1[N+9],Pow2[N+9];
int n,m,a,b;
 
ULL Hash(int a,int b,int c,int d){
  return s[b][d]-s[b][c-1]*Pow1[d-c+1]-s[a-1][d]*Pow2[b-a+1]+s[a-1][c-1]*Pow2[b-a+1]*Pow1[d-c+1];
}
 
struct Hash_table{
  int h[M+9];
  ULL v[M+9];
   
  void add(ULL &x,const ULL &b){x+=b;if (x>=M) x-=M;}
   
  void insert(ULL x){
    ULL t=x;
    for (x%=M;h[x]&&v[x]^t;add(x,5));
    ++h[x];v[x]=t;
  }
   
  bool find(ULL x){
    ULL t=x;
    for (x%=M;h[x]&&v[x]^t;add(x,5));
    return h[x]?1:0;
  }
   
}h;
ULL x,tmp[N+9];
 
Abigail into(){
  scanf("%d%d%d%d",&n,&m,&a,&b);
  Pow1[0]=Pow2[0]=1;
  for (int i=1;i<=m;++i) Pow1[i]=Pow1[i-1]*P1;
  for (int i=1;i<=n;++i) Pow2[i]=Pow2[i-1]*P2;
  for (int i=1;i<=n;++i)
    for (int j=1;j<=m;++j)
      s[i][j]=s[i][j-1]*P1+rc();
  for (int i=1;i<=n;++i)
    for (int j=1;j<=m;++j){
      s[i][j]+=s[i-1][j]*P2;
      if (i>=a&&j>=b) h.insert(Hash(i-a+1,i,j-b+1,j));
    }
}
 
Abigail getans(){
  int q;
  scanf("%d",&q);
  while (q--){
    for (int i=1;i<=a;++i){
      tmp[i]=0;
      for (int j=1;j<=b;++j)
        tmp[i]=tmp[i]*P1+rc(); 
    }
    x=0;
    for (int i=1;i<=a;++i)
      x=x*P2+tmp[i];
    puts(h.find(x)?"1":"0");
  }
}
 
int main(){
  into();
  getans();
  return 0; 
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值