扩大
缩小

洛谷 P3879【TJOI2010】阅读理解

题目链接:https://www.luogu.com.cn/problem/P3879

题意

有 $n$ 个句子,每句话里面有许多单词。

有 $m$ 次询问,每次询问提供一个单词,问这个单词在以上哪些句子中出现。

$n \le 10^3$,$m \le 10^4$,每个单词长度不超过 $20$,每个句子单词长度和不超过 $5 \times 10^3$。

我的解法

对于我这个如此之懒的人,肯定是不想写 Trie 这样的算法的,但又不想用 STL,所以就乱搞了个 hash。

具体操作为:

  • 设置若干个质数作为模数(质数不能太大,最好不要超过 $2 \times 10^4$);
  • 设置 hash 需要用到的进制数;
  • 每个句子都开一个 bool 数组记录,将某个单词的 hash 值对模数取模后放入下标,并且赋值为 $1$
  • 将每个句子中的每个单词进行 hash,并进行上述操作;
  • 将提问的 $m$ 个单词分别进行 hash,并枚举前 $n$ 个句子中,是否满足 hash 对这些质数取模后的下标,在该句子 bool 数组中均被赋值过

至于「若干个质数」到底是多少个,就需要进行一些尝试了。

检验的办法有很多种,如将包含 map 的暴力与正解程序进行对拍。

我经过多次尝试后,得到以下结果:

  • 设置 2 个质数检验:0 分;
  • 设置 4 个质数检验:30 分;
  • 设置 6 个质数检验:100 分。

原因也很简单,一个字符串(可以当做 26 进制数)任意排布,得到的 hash 值会非常大。

而我选择的质数都在 $10^4$ 上下,有非常大的几率发生 hash 冲突(可以说,如果只有一个模数,那每句话几乎都会发生冲突)。

所以,设置尽可能多的模数,才能使得 hash 冲突发生的概率尽可能小。

然后,就靠着这个办法,开 6 个质数检验的程序(不卡常)测试时间和只需要 208ms,在两千多份通过程序中是最快的。

代码(以下为 7 个模数的版本,加了 register 跑 223ms,欢迎各路神仙用数据卡我)

#include <cstring>
#include <cstdio>
#define INF 1e9
#define eps 1e-6
#define P 131
#define MOD1 9967
#define MOD2 8999
#define MOD3 8893
#define MOD4 11113
#define MOD5 12197
#define MOD6 12983
#define MOD7 13121
#define reg register
typedef long long ll;
typedef unsigned long long ull;

int n, m, l[1010], len;
char s[30];
bool b1[1010][9999], b2[1010][9999], b3[1010][9999], b4[1010][11999];
bool b5[1010][12999], b6[1010][12999], b7[1010][13999];
ull Hash, num1, num2, num3, num4, num5, num6, num7;

int main(){

    scanf("%d", &n);
    for(reg int i = 1; i <= n; i++){
        scanf("%d", &l[i]);
        for(reg int j = 1; j <= l[i]; j++){
            scanf("%s", s + 1), len = strlen(s + 1), Hash = 1;
            for(int k = 1; k <= len; k++)
                Hash = Hash * P + (s[k] - 'a' + 1);
            b1[i][Hash % MOD1] = 1, b2[i][Hash % MOD2] = 1;
            b3[i][Hash % MOD3] = 1, b4[i][Hash % MOD4] = 1;
            b5[i][Hash % MOD5] = 1, b6[i][Hash % MOD6] = 1;
            b7[i][Hash % MOD7] = 1;
        }
    }
    scanf("%d", &m);
    for(reg int i = 1; i <= m; i++){
        scanf("%s", s + 1), len = strlen(s + 1), Hash = 1;
        for(reg int j = 1; j <= len; j++)
            Hash = Hash * P + (s[j] - 'a' + 1);
        num1 = Hash % MOD1, num2 = Hash % MOD2, num3 = Hash % MOD3;
        num4 = Hash % MOD4, num5 = Hash % MOD5, num6 = Hash % MOD6;
        num7 = Hash % MOD7;
        for(reg int j = 1; j <= n; j++)
            if(b1[j][num1] && b2[j][num2] && b3[j][num3] && b4[j][num4] && b5[j][num5] && b6[j][num6] && b7[j][num7])
                printf("%d ", j);
        puts("");
    }

    return 0;
}
posted @ 2020-11-01 12:42  HoshizoraZ  阅读(215)  评论(0编辑  收藏  举报