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

本文介绍了一种利用字符串哈希解决大规模文本检索问题的方法。通过将字符串视为特定进制数,采用二重哈希技术高效地查找指定长度的单词,确保这些单词在多篇文章中出现的次数满足条件。

DescriptionDescription

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

Data ConstraintData Constraint

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

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

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

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

SolutionSolution

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

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

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

CodeCode

#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;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值