UVA 12206 Stammering Aliens(基于哈希值的LCP算法)
题意:给你一个字符串s和m,求出字符串中至少出现m次的最长子串.如果有多解,输出最长字符串的长度以及它出现的最大位置.
分析:其实本题可以用后缀数组来解.下面用哈希值来做.详见刘汝佳训练指南P225
首先对于一个长为n的字符串,我们定义它的哈希值为(下面的x值是人为设定的一个值):
s[0]+s[1]*x+s[2]*x^2+…s[n-1]*x^(n-1)
那么它的每个后缀[i,n-1]的哈希值为H[i]:
s[i]+s[i+1]*x+…s[n-1]*x^(n-1-i)
由上可以得到递推关系:
H[n]=0;
H[i]=H[i+1]*x+s[i]
那么对于该串s中的任意一段的哈希值为hash[i,L]:
hash[i,L]=s[i]+s[i+1]*x+s[i+2]*x^2+…s[i+L-1]*x^(L-1)=H[i]-H[i+L]*x^L
(验证一下上面的公式,看看是不是.其实主要是明白一段连续字符串的哈希值如何计算就可以,其他的递推公式是为了加快我们计算的.)
在程序中,我们二分答案,一一试探看看所有长度为l的子串中有没有出现m次的.我们只需要求出长度为l的所有子串的哈希值,然后将该哈希值数组排序,然后从小到大扫描,看看有没有同一个哈希值连续出现了m次的,如果有就表示长度l可以,并且记下该串最后出现的位置pos即可.
程序中我们用unsighed long long里保存哈希值,如果数据太大就自动溢出了,对于不同长度的串具有同一个哈希值的可能行很小很小.如果不放心还可以改变x的值再计算一次看看两者的哈希值是否相同,如果还是相同,那么出错的可能性就更小了.
AC代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 40000+1000;
typedef unsigned long long LL;
LL h[maxn],x;//后缀哈希值
char str[maxn];
int n,m,max_pos;
LL mi[maxn];//mi[i]的值是x的i次方的值
struct node
{
LL hash;//起点为pos且长len的串的哈希值
int pos;//起点位置
bool operator <(const node &b)const
{
return hash<b.hash ||(hash==b.hash && pos<b.pos);
}
}nodes[maxn];
bool check(int len)
{
for(int i=0;i+len-1<=n-1;i++)//共有n-len+1个长为len的连续子串
{
nodes[i].hash=h[i]-h[i+len]*mi[len];
nodes[i].pos=i;//起点位置
}
sort(nodes,nodes+n-len+1);
max_pos=-1;
int sum=0;
for(int i=0;i+len-1<=n-1;i++)
{
if(i==0 || nodes[i].hash==nodes[i-1].hash)
{
sum++;
if(sum>=m) max_pos=max(max_pos,nodes[i].pos);
}
else sum=1;
}
return max_pos>=0;
}
int main()
{
x=123;
while(scanf("%d",&m)==1&&m)
{
scanf("%s",str);
n=strlen(str);
h[n]=0;
for(int i=n-1;i>=0;i--)
h[i]=h[i+1]*x+str[i]-'a';
mi[0]=1;
for(int i=1;i<=n;i++)
mi[i]=mi[i-1]*x;
if(!check(1))
{
printf("none\n");
continue;
}
int min=1,max=n;
while(min<max)
{
int mid=min+(max-min+1)/2;
if(check(mid)) min=mid;
else max=mid-1;
}
check(min);
printf("%d %d\n",min,max_pos);
}
return 0;
}