题目链接: 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;
}