天天看点

UVAL 4513 (字符串Hash)

题目链接: https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2514

题目大意:给你一个数m和一个字符串,要你找出至少出现m次的最长子串。输出子串的长度和子串的最大的初始位置。

题目解析:就是求最长公共前缀。二分下答案,然后判断这个长度L是否满足出现次数大于m次。判断方法:计算出字符串从左到右的所有长度为L的子串的Hash值,如果Hash值相等的个数出现了大于等于m次,就说明这个L满足题意。

大白书 P225

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn = 40010;
const int x = 123;
unsigned long long H[maxn], xp[maxn], Hash[maxn];

char s[maxn];
int Rank[maxn];
int n,m,pos;
int cmp( int a,  int b)
{
    return Hash[a] < Hash[b] || (Hash[a] == Hash[b] && a < b);
}
//判断是否存在长度为L的字符串重复了m次
int solve(int L)
{
    for(int i = 0; i < n-L+1; i++)
    {
        Rank[i] = i;
        Hash[i] = H[i] - H[i+L] * xp[L];
    }

    sort(Rank, Rank+n-L+1, cmp);

    int cnt = 1;
    pos = -1;
    for(int i = 0; i < n-L+1; i++)
    {
        if(i == 0 || Hash[Rank[i]] != Hash[Rank[i-1]]) cnt = 1;
        else  cnt++;

        if(cnt >= m) pos = max(pos, Rank[i]);
    }
    return pos >= 0;

}


int main ()
{
    //freopen("in.txt", "r", stdin);

    while(scanf("%d", &m) , m)
    {
        scanf("%s", s);
        n = strlen(s);

        H[n] = 0;
        for(int i = n-1; i >= 0; i--)
            H[i] = H[i+1]*x + (s[i]-'a');

        xp[0] = 1;
        for(int i = 1; i <= n; i++)
            xp[i] = xp[i-1]*x;


        int L = 1, R = n+1;
        while(R - L > 1)
        {
            int M = L + (R-L) / 2;
            if(solve(M)) L = M;
            else R = M;
        }

        if(solve(L))
            printf("%d %d\n", L , pos);
        else
            printf("none\n");


    }
    return 0;
}