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; }