UVA11019题解

题意简述

给出一个 $n \times m$ 的矩阵 $M$,和一个 $x \times y$ 的模式矩阵 $N$ ,求 $N$ 在 $M$ 中的出现次数。

题目分析

此题可以使用字符串哈希。在讲这个题的具体做法之前我们首先来回顾一下一维情况下的字符串哈希是如何做的。字符串哈希是用来快速判断两个字符串是否相同的算法,具体过程如下:

  1. 首先是一个字符串 $s$(所有字符全部为小写字母,下同),比如 abcqwq
  2. 设定一个质数 $p$(值最好大一点)。
  3. 把这个字符串的每个字符换成 ASCII 码,而把整个字符串看作一个 $p$ 进制数,仍然以 abcqwq 为例,它的哈希值为 $97p^5+98p^4+99p^3+113p^2+119p+113$。
  4. unsigned long long 存储哈希值,这样就相当于自动取模 $2^{64}$。

以上便是计算一个字符串的哈希值的方法。如果两个字符串的哈希值相同,一般来说这两个字符串就是相同的,但是有特殊情况——哈希冲突。因为哈希值是取模过的,因此一定会存在哈希值相同的字符串,也就是哈希冲突,对于这种情况,可以通过把 $p$ 设得大一点来起到优化的作用,但是注意,哈希冲突是不可避免的(除非用散列表,但会耗时间)。

至于哈希如何解决字符串匹配问题,例如匹配一个字符串 $s_1$ 和模式串 $s_2$,如果通过计算 $s_1$ 中所有与 $s_2$ 长度相同的子串的哈希值再判断,则其时间复杂度与朴素算法所差无几。我们可以借助前缀和的思想,计算出 $s_1$ 的前缀的哈希值并递推地求,即 $Hash(i)=Hash(i-1) \times p+s_1[i]$。求出这些后便可以直接求出 $s_1$ 的任意子串的哈希值,比如一个子串 $s_1$ 从第 $i$个字符开始,长度为 $l$,它的哈希值就是 $Hash(i+l-1)-Hash(i-1) \times p^l$。那么现在我们只需要再计算出 $s_2$ 的哈希值,再枚举 $s_1$ 中与 $s_2$ 长度相同的子串与 $s_2$ 比较哈希值即可。

再讨论一下二维情况,和一维情况下的字符串哈希值求法相似,对于字符矩阵的哈希值可以看作有着两个进制的数。那么这又是个什么概念呢?是这样的:所谓两个进制则是指从行和列两个维度转移时的“进制”要不相同,否则哈希冲突的概率很大。具体计算方案如下:

  1. 首先是一个字符矩阵 $m$(所有字符仍全部为小写字母)。比如:
a b
q w
  1. 设定两个质数 $p_1$(进制一)与 $p_2$(进制二),最好一个大一个稍小。

  2. 以上图矩阵为例,其哈希值为 $97p_1^2p_2^2+98p_1+113p_2+119$。

  3. 仍用 unsigned long long 存储哈希值。

回到原问题中:如何解决矩阵匹配问题。与字符串匹配相似,仍是以递推的形式计算原矩阵 $M$ 的哈希值。在二维情况中,还是沿用了前缀和思想,相对应的也就是也就是 $Hash(i,j)=Hash(i-1,j) \times p_1+Hash(i,j-1) \times p_2-Hash(i-1,j-1) \times p_1 \times p_2+M_{i,j}$。那么与此对应,求 $M$ 中以 $(i,j)$ 为左上角,大小为 $m \times n$ 的子矩阵的哈希值也就是 $Hash(i,j)=Hash(i+m-1,j+n-1)-H(i-1,j+n-1) \times p_1^m-H(i+m-1,j-1) \times p_2^n+H(i-1,j-1) \times p_1^{m} \times p_2^{n}$。

那么至此,我们就已经可以通过枚举 $M$ 中与 $N$ 同样大小的子矩阵并与 $N$ 比较哈希值并计数来解决本题了。接下来讨论一下这种做法的时间复杂度。首先,计算 $M$ 和 $N$ 的哈希值的时间复杂度为 $O(mn+xy)$,枚举 $M$ 的子矩阵时间复杂度为 $O(mn)$,每次与 $N$ 比较哈希值的时间复杂度为 $O(1)$,另外,$p_1$、$p_2$ 的幂可以预处理以避免快速幂加上余外的 $\log(n)$ 和 $\log(m)$ 的时间复杂度。综上,本做法总时间复杂度为 $O(mn+xy)$。

然后给出代码

#include<bits/stdc++.h>
using namespace std;
char s[1010][1010],s2[1010][1010];
int n,m,n2,m2,ans,t;
unsigned long long h[1010][1010],h2[1010][1010],q1[1020100],q2[1020100],p1=40000021,p2=997;
int main()
{
    q1[0]=q2[0]=1;//零次方为1。
    for(int i=1;i<1020100;i++)
        q1[i]=q1[i-1]*p1,q2[i]=q2[i-1]*p2;//预处理p1、p2的幂。
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(int i=0;i<n;i++)
            scanf("%s",s[i]);
        for(int i=0;i<n;i++)
            for(int j=0;j<m;j++)
                h[i+1][j+1]=h[i][j+1]*p1+h[i+1][j]*p2-h[i][j]*p1*p2+s[i][j];//计算原矩阵的每个子矩阵的哈希值。
        scanf("%d%d",&n2,&m2);
        for(int i=0;i<n2;i++)
            scanf("%s",s2[i]);
        for(int i=0;i<n2;i++)
            for(int j=0;j<m2;j++)
                h2[i+1][j+1]=h2[i][j+1]*p1+h2[i+1][j]*p2-h2[i][j]*p1*p2+s2[i][j];//计算模式矩阵的哈希值。
        ans=0;
        for(int i=0;i<n-n2+1;i++)
            for(int j=0;j<m-m2+1;j++)//在原矩阵中枚举与模式矩阵同样大小的子矩阵。
                if(h[i+n2][j+m2]-h[i][j+m2]*q1[n2]-h[i+n2][j]*q2[m2]+h[i][j]*q1[n2]*q2[m2]==h2[n2][m2])
                    ans++;
        printf("%d\n",ans);
    }
    return 0;
}

update

本文最初发布于 2021.08.17,其时本人还未上初中,用语多有不严谨、幼稚之处,Latex 也有使用错误。经众多网友反馈,于 2023.8.29 修改重新上传。

posted @ 2021-08-17 13:04  Hadtsti  阅读(2)  评论(0编辑  收藏  举报  来源