@(K ACMer)
题意:
给你一个字符串,其中由nc个不同的字符组成.问你它的长度为n的不同的子序列有多少个?
分析:
- 咋一看最直接的想法就是每一个子串都加入set中,然后最后看set中有多少个字符串就可以了.复杂度是 O(n∗strlen∗logn) 显然超时.
- 那么有什么优化呢?
- 我们可以看出每次往set中添一个字符串,这个字符串会和已经有的字符串比较是否相等,比较的时候就是逐个比较ASCII码.显然这个是非常高时间复杂度的,有没有不要每次的比较那么多次呢?
- 这是字符串hash就出场了,把一个字符串转换成一个对应的数字,这样就可以快速比较这两个字符串相同与否了.但是要怎样确保两个不同的字符串产生的hash值就一定不相同呢?
- 这时候就需要用到进制数的思想了,我们可以知道一个n进制数,它其中的字符有n个,n个字符组成的数,一定是特别的.且它的每一位是有不同的位权的.这样对于一个有nc个不同字符的字符串,我们可以对其中的每一个不同的字符给它一个从0开始的编码,最后把它表示成一个nc进制的数,算出这个数的值,就可以确保每一个字符串对应的hash值唯一.有一种hash算法就是转换成131进制数,其实也是这个道理,不过这个题必须压缩为nc进制,因为进制越大最后的数值也就越大很容易溢出,溢出之后的值很大程度上是由字符串前面几位的值确定的,所以就很容易碰撞.
- 这里还要继续优化,这里我们没算出一个子串的hash值不是把它加入set里,而是打了一个hash表来容纳这些字符串.实现了 O(n) 的复杂度解决问题.
- 最后我再计算每一个子串的时候利用了一点DP的思想.虽然并没有使94ms的时间更快=_=
code:
#include <iostream>
#include <cstdio>
#include <set>
#include <cstring>
using namespace std;
const int M = 17000000;
int n, nc, mod = int(1e9) +1123;
unsigned long long hx, pow[M];
char s[M];
bool hasht[M];
int key[300];
int main(void) {
while (~scanf("%d%d%s", &n, &nc, s)) {
int len = strlen(s);
memset(hasht, false, sizeof(hasht));
memset(key, 0, sizeof(key));
int id = 0;
for (int i = 0; i < len; i++) {
if (!key[s[i]]) {
key[s[i]] = id++;
}
}
pow[0] = 1;
for (int i = 1; i < n; i++) {
pow[i] = pow[i - 1] * nc;
}
int ans = 1;
hx = 0;
for (int j = 0; j < n; j++) {
hx += key[s[j]] * pow[j];
}
hasht[hx % (M - 13)] = true;
for (int i = n; i < len; i++) {
hx -= key[s[i - n]];
hx /= nc;
hx += key[s[i]] * pow[n - 1];
if (!hasht[hx % (M - 13)]) ans++;
hasht[hx % (M - 13)] = true;
}
printf("%d\n", ans);
}
return 0;
}