题意: 给一个字符串,和m,l, 找出这样的子串: 长度为m*l, 由m个长度为l的串组成,每个串都不同。(s,size()<10^5)
字符串hash典例。 这里用的是bkdrhash 法。也是最常用的冲突最少的一种。原理:把字符串和数值对应。这里用base=31(一般用质数),
先是扫一遍,处理处每个位子到结尾构成的串的hash值(倒过来的),然后长度为l的子串的haash值就好算了。
之后枚举开头l个,每次向后翻滚,复杂度max(L*M, L*(S.SIZE/M))可以过,这里用了map判重下。若枚举开头扫一遍,姿势不优越过不了,极限可能:m=50000,l=1,复杂度(s,size*m)会超时。
关键一:那里求hash值的时候+1,否则100,10这种hash值一样。
开始担心这样减hash值会因为爆出现负值。其实不然:其一, unsigned long long ,自动取模(最近在学汇编,的确如此啊),
其二:因为每次从后面向前推导:hash[i] = hash[i+1]*base+s[i]-'a'+1; ,本质自动取模,所以:hash[i]=s[i]-'a'+1+hash[i+l]*nbase[l] (每步自动取模),由于 s[i]-'a'+1 非负,所以有
hash[i]》hash[i+l]*nbase[l]
(ps:感谢puyijie学长的指导)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<string>
typedef unsigned long long ull;
using namespace std;
const int maxn= 100050;
const ull base =31;
ull nbase[maxn],hash[maxn];
int m,l;
map<ull, int> mp;
int main()
{
ull tmp;
nbase[0] = 1;
for (int i = 1;i<maxn; i++)
{
nbase[i]=nbase[i-1]*base;
}
while (~scanf("%d%d",&m,&l))
{
string s;
cin>>s;
int slen=s.size();
hash[slen] = 0;
for (int i = slen-1; i >= 0; i--)
hash[i] = hash[i+1]*base+s[i]-'a'+1; //关键1
int ans = 0;
for (int i = 0; i<l&&i+m*l<=slen; i++)
{
mp.clear();
for (int j = i; j<i+m*l; j += l)
{
tmp = hash[j] - hash[j+l]*nbase[l];
mp[tmp]++;
}
if (mp.size() ==m)
ans++;
for (int j=i+m*l; j+l<=slen; j +=l)
{
tmp = hash[j-m*l] - hash[j-(m-1)*l]*nbase[l];
mp[tmp]--;
if (mp[tmp] == 0)
mp.erase(tmp);
tmp = hash[j] - hash[j+l]*nbase[l];
mp[tmp]++;
if (mp.size() == m)
ans++;
}
}
printf("%d\n", ans);
}
return 0;
}