二维哈希讲解-And-AcWing-156. 矩阵-题解

Posted Tisfy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二维哈希讲解-And-AcWing-156. 矩阵-题解相关的知识,希望对你有一定的参考价值。

如果还不清楚什么是哈希算法,可以点击这里

二维哈希思想

说一下我对二维哈希算法的理解

之前,我们已经可以把一维数组映射成一个数了。如下:

数组名元素哈希值
A1,5,6,9,8,4,0,5,6,8,4,5,6,9,8,7,415698405684569874
B5,9,8,4,7,0,2,5,6,9,4,5,6,9,8,4,559847025694569845
C1,5,6,9,8,4,0,5,6,8,4,5,6,9,8,7,415698405684569874

现在,我们来把一个二维数组映射成一个数。
为了便于理解,我们仍先用10进制,把较少的数据映射成一个数。

数组名元素
A7 9 3
1 6 5
5 7 2
B4 3
5 0

二维数组有什么,视为一维数组就好了。
可以映射为:

数组名元素哈希值
A7 9 3
1 6 5
5 7 2
793165572
B4 3
5 0
4350

方法也不难

//假设是n行m列的矩阵A
typedef unsigned long long ull;
ull Hash()
{
	ull ans=0;
	int p=10;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{
			ans=ans*p+A[i][j];
		}
	}
	return ans;
}

仅仅知道这些还不够,我们还要知道如何快速求得大矩阵A中一个固定大小的小矩阵的哈希值:

数组名元素
A7 9 3
1 6 5
5 7 2

现在,让你算出A中所有大小为 2 ∗ 2 2*2 22的子矩阵的哈希值:
A中有4个大小为 2 ∗ 2 2*2 22的子矩阵,分别为:

方位元素
原矩阵7 9 3
1 6 5
5 7 2
左上角7 9
1 6
右上角9 3
6 5
左下角1 6
5 7
左上角6 5
7 2

通过常规方法,我们可以计算出这4个子矩阵的哈希值:

方位元素计算过程哈希值
左上角7 9
1 6
7+9*10+1*10*10+6*10*10*107916
右上角9 3
6 5
9+3*10+6*10*10+5*10*10*109365
左下角1 6
5 7
1+6*10+5*10*10+7*10*10*101657
左上角6 5
7 2
6+5*10+7*10*10+2*10*10*106572

为什么有删除线?
是因为这样计算太慢了。

这是要找 2 ∗ 2 2*2 22的子矩阵,每次计算需要 2 ∗ 2 = 4 2*2=4 22=4次。( n ∗ m n*m nm的子矩阵每次要计算 n ∗ m n*m nm次)。如果 n n n m m m大一些呢?就会很慢了。


左上角和左下角的子矩阵,有一行是重复的。
所以能不能根据上一个子矩阵,快速( O ( 1 ) O(1) O(1))求得新的子矩阵呢?

比较左上角和左下角的子矩阵,它们有共同的一行: 1    6 1\\ \\ 6 1  6。它们的哈希值里也都有相邻的两个数 16 16 16

方位元素哈希值
左上角7 9
1 6
7916
左下角1 6
5 7
1657

那么怎么由左上角的 7916 7916 7916直接快速得到左下角的 1657 1657 1657呢?
7916 ∗ 1 0 2 + 57 − 79 ∗ 1 0 2 ∗ 2 = 791600 + 57 − 790000 = 1657 7916*10^2+57-79*10^{2*2}=791600+57-790000=1657 7916102+57791022=791600+57790000=1657
其中,加上的 57 57 57是左下角比左上角多出的新的一行的 5    7 5\\ \\ 7 5  7的哈希值;
减去的 79 79 79是左下角没有而在左上角出现过的一行 7    9 7\\ \\ 9 7  9的哈希值。


而一行中某段的哈希值如何快速求得呢?

对于一行中的某些连续的值,用a[i]代表前i个数的哈希值,那么从l到r的这些数的哈希值就是 a [ r ] − a [ l − 1 ] ∗ p r − l + 1 a[r]-a[l-1]*p^{r-l+1} a[r]a[l1]prl+1
例如:一行有4个元素: 4   5   8   3 4\\ 5\\ 8\\ 3 4 5 8 3,先预处理得到 a [ ] : { 4 , 45 , 458 , 4583 } a[]:\\{4,45,458,4583\\} a[]:{4,45,458,4583}
现在突然让你求第二个元素到第三个元素的哈希( 58 58 58),公式为: 458 − 4 ∗ 1 0 2 = 58 458-4*10^{2}=58 4584102=58 l = 2 , r = 3 l=2,r=3 l=2,r=3)。

所以,给定大矩阵A后,我们先对其每一行进行预处理,求出每一行的前i个元素的哈希。
之后求所有的大小为 a ∗ b a*b ab的矩阵,两层循环:第一层是列,第二层是行。
对于第一层的这一列,下面子矩阵的哈希能够根据上面子矩阵的哈希快速求得。


又双叒叕认为讲得差不多了,不如根据一道题来实战一下:

AcWing-156. 矩阵

Time Limit: 2s
Memory Limit: 64M
传送门

Description

给定一个 M M M N N N 列的 01 01 01 矩阵(只包含数字 0 0 0 1 1 1 的矩阵),再执行 Q Q Q 次询问,每次询问给出一个 A A A B B B 列的 01 01 01 矩阵,求该矩阵是否在原矩阵中出现过。

Input

第一行四个整数 M , N , A , B M,N,A,B M,N,A,B

接下来一个 M M M N N N 列的 01 01 01 矩阵,数字之间没有空格。

接下来一个整数 Q Q Q

接下来 Q Q Q A A A B B B 列的 01 01 01 矩阵,数字之间没有空格。

A ≤ 100 , M , N , B ≤ 1000 , Q ≤ 1000 A≤100 ,M,N,B≤1000,Q≤1000 A100M,N,B1000Q1000

Output

对于每个询问,输出 1 表示出现过,0 表示没有出现过。

Sample Input

3 3 2 2
111
000
111
3
11
00
11
11
00
11

Sample Output

1
0
1

题目大意

给你一个大矩阵,之后给你一些小矩阵,问小矩阵在大矩阵中有没有出现过。


解题思路

因为小矩阵的大小是确定的,所以我们先把大矩阵的每个大小为小矩阵的子矩阵的哈希值计算出来,并保存。
之后没输入一个小矩阵,就计算出它的哈希值,看看有没有在大矩阵的子矩阵中出现过。

之后的看看注释理解理解吧

AC代码

#include<bits/stdc++.h>
#include<set>

using namespace std;
typedef unsigned long long ull;
const int p=131;//131进制
ull ma[1010][1010];//ma[i][j]代表大矩阵第i行的前j个元素的哈希值
ull power[1000010];//power[i]代表131的i次方
ull getLine(int x,int l,int r)//第x行从l到r的hash值
{
    return ma[x][r]-ma[x][l-1]*power[r-l+1];//这个公式在上文有推导过
}
int main()
{
    int a,b,n,m;
    cin>>m>>n>>a>>b;//大矩阵和小矩阵的数据量
    power[0]=1;//131的0次幂是1
    for(int i=1;i<=n*m;i++)power[i]=power[i-1]*p;//计算出次幂
    for(int i=1;i<=n;i++)//有前缀一般从小标1开始
    {
        string s;
        cin>>s;//输入这一行
        for(int j=0;j<m;j++)
            ma[i][j+1]=ma[i][j]*p+s[j]-'0';//次公式上文有推导过,前面的*进制+这个
    }
    set<ull>S;//用来记录一个哈希值是否出现过
    for(int i=b;i<=m;i++)//列:b列到m列;i枚举的子矩阵的最右边的一列
    {
        ull s=0;//初始值是0
        int l=i-b+1,r=i;//列的左边是l,右边是r;r就是i,一行有b个,所以l=i-b+1
        for(int j=1;j<=n;j++)//j是行,大矩阵的从第1行到最后一行
        {
            s=s*power[b]+getLine(j,l,r);//加上得到这一行从l到r的哈希
            if(j-a>0)s-=getLine(j-a,l,r)*power[a*b];//如果这不是前几行,而是已经>a+1行了,因为只求需要a行的子矩阵的哈希,所以就需要把多余a行的上一行减掉
            if(j-a>=0)S.insert(s);//如果已经有a行了,就是一个合格大小的子矩阵,记录下来
        }
    }
    int q;
    cin>>q;//q个小矩阵
    while(q--)
    {
        ull s=0;//对每个小矩阵的哈希,初始值是0
        string str;
        for(int i=0;i<a;i++)//每一行
        {
            cin>>str;//输入这一行
            for(int j=0;j<b;j++)//这一行的每个元素
            {
                s=s*p+str[j]-'0';//新哈希值=老哈希值*进制+这个元素对应的值
            }
        }
        puts(S.count(s)?"1":"0");//S中有s吗 ?如果有输出1 :否则输出0
    }
    return 0;
}

掌握得怎么样,还想再练练吗?


原创不易,转载请附上原文链接哦~
Tisfy:https://letmefly.blog.csdn.net/article/details/116563356

以上是关于二维哈希讲解-And-AcWing-156. 矩阵-题解的主要内容,如果未能解决你的问题,请参考以下文章

Java中二维数组是什么?如何使用?概念!语法!案例!三方面讲解。

POJ3690 Constellations

均值哈希算法和感知哈希算法

python数据分析学习pandas二维工具DataFrame讲解

Python刷题实用小tips集合(持续更新)

详解矩阵乘法