算法 - 字符串算法 - 字符串哈希与经典例题《好文章》
nodgd 写了一篇文章,自认为这是一篇好文章。nodgd 的文章由 \(n\) 个小写英文字母组成。文章的一个子串指的是文章中的一段连续的字母,子串的长度就是这一段的字母个数。nodgd 在文章中用了排比、对偶、前后照应之类的手法,所以就有很多个子串是相同或者相近的。为了向大家证明这是一篇好文章,nodgd 决定给自己的文章进行评分。nodgd 首先确定了一个整数 \(m\),然后统计出文章中有多少个不相同的长度为 \(m\) 的子串,这个数量就是文章的评分。然而,nodgd 懒得老老实实计算这个评分了,就把任务丢给了你。
输入格式
第一行包含两个整数 \(n,m\),表示文章的长度和需要统计的子串长度。
第二行包含一个长度为 \(n\) 的只包含小写字母的字符串。
输出格式
输出一行一个整数,表示文章的评分。
输入输出样例
样例 1
输入
5 3
aaaab
输出
2
长度为 \(3\) 的子串有 \(3\) 个,分别是 aaa、aaa、aab,其中不同的只有 \(2\) 个。
样例 2
输入
9 3
abcabacba
输出
7
共有7个长度为3的子串,每个长度为3的子串都不同。
数据范围
于 \(30\%\) 的数据,\(1 ≤ m ≤ n ≤ 200\);
于 \(50\%\) 的数据,\(1 ≤ m ≤ n ≤ 2000\);
于 \(70\%\) 的数据,\(1 ≤ m ≤ 50 ≤ n ≤ 200000\);
于 \(100\%\) 的数据,\(1 ≤ m ≤ n ≤ 200000\);
题解
使用双哈希,这里我们开两个hash表,然后给同一个字符串附上两个hash值,在判断两个字符串是否相同时,必须两个hash值都相同才行。
#include<cmath>
#include<cstdio>
const int maxm = 200010;
const int mod1 = 1e9 + 7, mod2 = 1e9 + 9;
long long hash_a[maxm], hash_b[maxm];
long long Hash_a[maxm], Hash_b[maxm]; // 字符串 s[1..i] 的哈希值
long long Hash_pow_a[maxm], Hash_pow_b[maxm]; // 指数数组
int n, m, ans;
char str[maxm];
// 利用给定的哈希算法参数,算出给定串的哈希值(包括哈希数组和指数数组)
void Init(int p1, int P1, int p2, int P2)
{
for (int i = 1; i <= n; i++)
{
// 获取第一个哈希的结果
Hash_a[i] = (1LL * Hash_a[i - 1] * p1 + 1LL * (str[i] - 'a' + 1)) % P1;
Hash_pow_a[i] = 1LL * Hash_pow_a[i - 1] * p1 % P1;
// 获取第二个哈希的结果
Hash_b[i] = (1LL * Hash_b[i - 1] * p2 + 1LL * (str[i] - 'a' + 1)) % P2;
Hash_pow_b[i] = 1LL * Hash_pow_b[i - 1] * p2 % P2;
}
}
// 获取子串的哈希值
int GetHash(int l, int r, int p, int P, int k)
{
if (k == 1) { // 说明函数请求获取该子串的第一个哈希值
return (Hash_a[r] - 1LL * Hash_a[l - 1] * Hash_pow_a[r - l + 1] % P + P) % P;
}
else if (k == 2){ // 说明函数请求获取该子串的第二个哈希值
return (Hash_b[r] - 1LL * Hash_b[l - 1] * Hash_pow_b[r - l + 1] % P + P) % P;
}
}
//快排,可以使用sort,开结构体,然后根据HashA的值排序
void qst(int l, int r)
{
int i, j, m, mm, t;
i = l; j = r;
m = hash_a[(i + j) >> 1];//位运算,相当于除以2
mm = hash_b[(i + j) >> 1];
while (i <= j)
{
while (hash_a[i] < m || (hash_a[i] == m && hash_b[i] < mm))i++;
while (hash_a[j] > m || (hash_a[j] == m && hash_b[j] > mm))j--;
if (i <= j)
{
t = hash_a[i]; hash_a[i] = hash_a[j]; hash_a[j] = t;
t = hash_b[i]; hash_b[i] = hash_b[j]; hash_b[j] = t;
i++; j--;
}
}
if (i < r) qst(i, r);
if (l < j) qst(l, j);
}
int main()
{
scanf("%d%d", &n, &m);
scanf("%s", str + 1);
Hash_pow_a[0] = 1;
Hash_pow_b[0] = 1;
Init(61, mod1, 97, mod2);
for (int i = 1; i <= n - m + 1; i++)
{
hash_a[i] = GetHash(i, i + m - 1, 61, mod1, 1);
hash_b[i] = GetHash(i, i + m - 1, 97, mod2, 2);
}
qst(1, n - m + 1);
for (int i = 1; i <= n - m + 1; i++)
{
if (hash_a[i] == hash_a[i - 1] && hash_b[i] == hash_b[i - 1])
continue;
ans++;
}
printf("%d\n", ans);
return 0;
}