ACwing 841 - 字符串哈希(字符串hash)

给定一个长度为n的字符串,再给定m个询问,每个询问包含四个整数 l1,r1,l2,r2 ,请你判断[ l1,r1 ]和[ l2,r2 ]这两个区间所包含的字符串子串是否完全相同。

字符串中只包含大小写英文字母和数字。

输入格式

第一行包含整数n和m,表示字符串长度和询问次数。

第二行包含一个长度为n的字符串,字符串中只包含大小写英文字母和数字。

接下来m行,每行包含四个整数 l1,r1,l2,r2 ,表示一次询问所涉及的两个区间。

注意,字符串的位置从1开始编号。

输出格式

对于每个询问输出一个结果,如果两个字符串子串完全相同则输出“Yes”,否则输出“No”。

每个结果占一行。

数据范围

1 ≤ n, m ≤ 105
输入样例:
8 3
aabbaabb
1 3 5 7
1 3 6 8
1 2 1 2

输出样例:

Yes
No
Yes

题目大意:

输入n和m,表示有一个长度为 n 的字符串和 m 个询问,再输入长度为 n 的字符串,接下来是m个询问,每次询问给出两个区间[l1, r1], [l2, r2] ,询问这个串中这两个区间是否完全相等,并输出yes or no。

解题思路:

字符串hash模板题,判断串中两个子串是否相等时,可以用字符串hash去解决,接下来介绍字符串hash的具体思路:

字符串hash:

开两个数组h[] 和p[] , h i 表示前 i 个字符的哈希值,p i 表示 P 的 i 次方,对于前 n 个字符的哈希值是这样求的,将其看作 n 位的 p 进制数模除一个数,假设ABCD 则 h[4] 的哈希值就是 A * p3 + B * p2 + C * p1 + D * p0, 关于取模,这里一般选择264,然后数组开unsigned long long , 这样不用刻意取模,因为64位无符号 int 的溢出机制就是对 264 取模。先预处理p[n] 和 h[n],p进制数,前面为高位,则递推公式为p[i] = p[i - 1] * P,注意p[0]应为1,然后h[i] = h[i - 1] * p + str[i](默认将其映射为ASCII码,一定注意不能映射成0,因为这样A,AAAAAA,AAAAAA的哈希值都一样了),这里的p进制,p为131或13331, 能最大化减少冲突(因为哈希表是有可能出现冲突的),之后对于两个区间的判断,实质上就是判断两个区间的哈希值是否相等,对于区间[l, r] , 我们知道h[r] 和 h[l - 1] 的哈希值,但是高位对应的不一样,所以要将h[r - 1] * pr-(l-1) = h[r - 1] * pr-l+1, 之后比较两个区间的哈希值即可。

Code:

#include <iostream>

using namespace std;

typedef unsigned long long ull;

const int N = 1e5 + 50;

ull h[N], p[N];
char str[N];

ull geth(int l, int r)//求区间哈希值
{
    return h[r] - h[l - 1] * p[r - l + 1];
}

int main()
{
    int n, m;
    scanf("%d%d%s", &n, &m, str + 1);
    p[0] = 1;
    
    for (int i = 1; i <= n; i ++)//递推一下h 和 p P取131 || 13331
    {
        p[i] = p[i - 1] * 131;
        h[i] = h[i - 1] * 131 + str[i];
    }
    
    while (m--)
    {
        int l1, r1, l2, r2;
        scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
        
        puts(geth(l1, r1) == geth(l2, r2) ? "Yes" : "No");//判断两个区间的哈希值是否相同
    }
    
    return 0;
}
posted @ 2020-08-20 11:31  Hayasaka  阅读(71)  评论(0编辑  收藏  举报