HDU4821
本题题意就是给你一个字符串,问你该字符串具有多少个子串满足
长度为m*l,而且可以拆成m个长度为l的不相同子串。
定长字符串问题,很明显是可以hash+尺取的,我们可以枚举l个子串的起点,之后算出当前m*l的字符串的情况,之后不断往后尺取就可以了
复杂度是
o(l∗(len/l)∗log(m))
o
(
l
∗
(
l
e
n
/
l
)
∗
l
o
g
(
m
)
)
因为尺取的过程每次向后跳l个字符,log是map带来的所以复杂度是可以的
然而如果枚举所有起点的m*l字符串验证复杂度就会变成
o(len∗l)
o
(
l
e
n
∗
l
)
是会超时的
这就是利用hash+尺取带来的优势。
HDU4821代码
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<map>
using namespace std;
typedef unsigned long long ull;
#define dbg(x) cout<<#x<<" = "<<x<<endl;
#define Get_hash(i,L) hash_[i]-hash_[i+L]*xp[L]
const int maxn = 1e5+5;
ull xp[maxn],hash_[maxn];
ull x[maxn];
char str[maxn];
void init()
{
xp[0]=1;
for(int i=1;i<maxn;i++)
xp[i]=xp[i-1]*13331;
return ;
}
int Make_hash(char str[])
{
int len=strlen(str);
hash_[len]=0;
for(int i=len-1;i>=0;i--)
hash_[i]=hash_[i+1]*13331+(str[i]-'a'+1);
return len;
}
map<ull,int> mp;
int main()
{
init();
int m,l;
while(scanf("%d%d",&m,&l)!=EOF)
{
int cnt=0;
scanf("%s",str);
int len=Make_hash(str);
for(int i=0;i<len-l+1;i++)
{
x[i]=Get_hash(i,l);
}
for(int i=0;i<l&&i+m*l<=len;i++)//枚举起点,注意范围
{
mp.clear();
int ans=0;
for(int j=i;j<i+m*l;j+=l)
{
if(mp[x[j]]==0)
{
ans++;
}
mp[x[j]]++;
}
if(ans==m) cnt++;//以上是用来计算出第一个完整的m*l块的情况,之后尺取就可以了
for(int j=i+m*l;j+l<=len;j+=l)//注意范围
{
ull tmp=x[j-m*l];
mp[tmp]--;//去掉当前第一个长度为l的子串
if(mp[tmp]==0) ans--;
tmp=x[j];//加上当前长度l的子串
if(mp[tmp]==0)
{
ans++;
}
mp[tmp]++;
if(ans==m) cnt++;//当前的m个均不相同
}
}
printf("%d\n",cnt);
}
return 0;
}