一.字符串哈希
1.前言
这是一种十分重要的算法,它能够迅速计算出字符串里的存在性问题,代码也并不长,挺好写的
2.表示方式
用数组hash[i]表示字符串i哈希之后的值,用x数组表示原字符串,则有:
hash[i] = ( hash[i - 1] * p + ( x[i] - 'a' + 1 )) % mod
p表示一个进制关系,越大越好,这样可以避免重复。最好用unsigned long long,可以自然溢出,它主动模(2^64 - 1)
3.性质
我们这样表示这个字符串:
x[i] = s1s2s3......si
那么有:
x[1] = s1
x[2] = s1s2
x[3] = s1s2s3
x[4] = s1s2s3s4
其中一个字符串的表示方法就是:
sl......sr = hash[r] - hash[l - 1] * p ^ (r - l + 1)
可以通过观察证明,好那么字符串哈希最重要的两个公式就介绍完了:
哈希初始化 hash[i] = ( hash[i - 1] * p + ( x[i] - 'a' + 1 )) % mod
截取字符串中一段 sl......sr = hash[r] - hash[l - 1] * p ^ (r - l + 1)
上例题QWQ
二.典例:COCI2017 Lozinke
1.题目
2.题解
这道题目在有了字符串哈希的基础上就很简单了吧,再说每个字符串长度不超过10.
现在来说一下详细的实现步骤:
1.初始化哈希每个字符串
2.对哈希出来的值进行排序和去重
3.枚举每一个字符串里的子串,统计每个子串的出现次数
4.枚举每个字符串,看每个字符串出现了多少次,累加出现次数减一
3.Code
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define M 20005
#define P 117
#define ULL unsigned long long
ULL p[20], lisan[M * 100], vap[M][20];
int n, len[M], tot, vis[M * 100], cnt[M * 100], ans;
char a[M][15];
ULL get_hash (int l, int r, int cur){
return vap[cur][r] - vap[cur][l - 1] * p[r - l + 1];
}
int main (){
scanf ("%d", &n);
p[0] = 1;
for (int i = 1; i <= 10; i ++)
p[i] = p[i - 1] * P;
for (int i = 1; i <= n; i ++){
scanf ("%s", a[i] + 1);
len[i] = strlen (a[i] + 1);
for (int j = 1; j <= len[i]; j ++)
vap[i][j] = vap[i][j - 1] * P + (a[i][j] - 'a' + 1);
for (int j = 1; j <= len[i]; j ++){
for (int k = j; k <= len[i]; k ++){
lisan[++ tot] = get_hash (j, k, i);
}
}
}
sort (lisan + 1, lisan + 1 + tot);
int tmp = unique (lisan + 1, lisan + 1 + tot) - lisan;
tot = tmp;
for (int i = 1; i <= n; i ++){
for (int j = 1; j <= len[i]; j ++){
for (int k = j; k <= len[i]; k ++){
int now = lower_bound (lisan + 1, lisan + 1 + tot, get_hash (j, k, i)) - lisan;//找位置,离散化
if (vis[now] != i)
vis[now] = i, cnt[now] ++;
}
}
}
for (int i = 1; i <= n; i ++){
int now = lower_bound (lisan + 1, lisan + 1 + tot, get_hash (1, len[i], i)) - lisan;
ans += cnt[now] - 1;
}
printf ("%d\n", ans);
return 0;
}