UVA 12206 Stammering Aliens(基于哈希值的LCP算法)

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


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值