【模板】字符串哈希 (【JZOJ3870】单词检索(search))

D e s c r i p t i o n

小可可是学校图书馆的管理员,现在他接手了一个十分棘手的任务。 由于学校需要一些材料,校长需要在文章中检索一些信息。校长一共给了小可可 N 篇文章,每篇文章为一个字符串。现在,校长需要他找到这样的单词,它至少在这 N 篇文章中的 M 篇文章里出现过,且单词长度为 L 。可是,工作量十分庞大,但校长又急需小可可完成这项任务。 现在他向你求助,需要你编写程序完成这项艰巨的任务。

D a t a   C o n s t r a i n t

对于 20 % 的数据有 1 N , M 10

对于 60 % 的数据有 1 N , M 100

对于 100 % 的数据有 1 N , M 2000 L 1000

每篇文章长度不大于 1000 ,均有小写字母组成。

S o l u t i o n

想要拿满分就要用字符串哈希,只于你想拿个部分分就像我一样在考场上写个 T r i e 好了。

字符串哈希一般都是二重哈希。有几个步骤叙述一下:

  • 我们先把整个字符串看成一个 27 进制数(其实更高也可以,不过 27 已经够了,主要目的是把每个字母都看成一个别的进制下的“数“),先开出一张 27 的幂次的表来以便转换。
  • 假设字符串的长度为 L ,则我们要对 L l + 1 个长度为 l 的字符串进行字符串哈希。一个一个弄过来复杂度是 O ( ( L l + 1 ) l ) 明显时间不够,那我们就观察一下,假设这个字符串是 a 1 a 2 a 3 a L 。我们截取的一段的最后一个一个字母是 a p ,则我们截取的字符串是 a p l + 1 a p 1 a p 。由于这个数拿出来就是 P = a p 27 0 + a p 1 27 1 + + a p l + 1 27 l 1 。考虑到字符串 a 1 a 2 a p l 的值是 M = a p l 27 0 + a p l 1 27 1 + + a 1 27 p l 1 ,又考虑到字符串 a 1 a 2 a p 的值是 N = a p 27 0 + a p 1 27 1 + + a 1 27 p 1 。 我们就可以观察到 M 27 l + P = N P = N M 27 l 。由此,我们就可用一种类似前缀和的方式就可以在 O ( L l + 1 ) 的时间内把所有的值全部计算出来计算出来。
  • 有了上面的值,我们就可以进行第一次哈希,由于题目的原因,一个串里面有很多一模一样的子串是不可以重复计算的,所以第一次不可直接累加上去。
  • 接下来,就对那些这个串中存在的哈希值进行第二次哈希将其并入总表,这次就需要累加计数。
  • 现在,二重哈希的过程就结束了,现在要做的就是遍历哈希总表,统计答案。

C o d e

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
#define BASE 27
#define MAXN 2005
#define MAXL 1005
#define MAXSIZE 10003
#define MAXSIZE_DOUBLE 10000007
#define P 1000000003
LL table[MAXSIZE], table_double[MAXSIZE_DOUBLE][2];
LL pow[MAXL], sum[MAXN];
char word[MAXN];
void hash(LL value) {
    LL key = value % MAXSIZE;
    while (table[key] && table[key] != value) 
        key = (key + 1) % MAXSIZE;
    table[key] = value;
}
void hash_double(LL value) {
    LL key = value % MAXSIZE_DOUBLE;
    while (table_double[key][0] && table_double[key][0] != value)
        key = (key + 1) % MAXSIZE_DOUBLE;
    table_double[key][0] = value;
    table_double[key][1]++;
}
int main() {
    int n, m, l;
    scanf("%d%d%d", &n, &m, &l);
    pow[0] = 1;
    for (int i = 1; i < MAXL; i++) 
        pow[i] = pow[i - 1] * BASE % P;
    for (int i = 1; i <= n; i++) {
        memset(table, 0, sizeof table);
        scanf("%s", word + 1);
        int len = strlen(word + 1);
        for (int j = 1; j <= len; j++) 
            sum[j] = (sum[j - 1] * BASE + (word[j] - 'a')) % P;
        for (int j = l; j <= len; j++) 
            hash((sum[j] - sum[j - l] * pow[l] % P + P) % P);
        for (int j = 1; j < MAXSIZE; j++) 
            if (table[j])
                hash_double(table[j]);
    }
    LL ans = 0;
    for (int i = 1; i < MAXSIZE_DOUBLE; i++) 
        if (table_double[i][1] >= m)
            ans++;
    printf("%lld\n", ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Diogenes_/article/details/81037792