题目链接:https://www.luogu.com.cn/problem/P3879
题意
有 $n$ 个句子,每句话里面有许多单词。
有 $m$ 次询问,每次询问提供一个单词,问这个单词在以上哪些句子中出现。
$n le 10^3$,$m le 10^4$,每个单词长度不超过 $20$,每个句子单词长度和不超过 $5 imes 10^3$。
我的解法
对于我这个如此之懒的人,肯定是不想写 Trie 这样的算法的,但又不想用 STL,所以就乱搞了个 hash。
具体操作为:
- 设置若干个质数作为模数(质数不能太大,最好不要超过 $2 imes 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; }