题目 https://cn.vjudge.net/problem/POJ-3261
题意:
求可重叠的最长重复子串,但有一个限制条件。。要至少重复k次
思路
只需要求 height 数组
里的最大值即可。首先求最长重复子串,等价于求两个后缀的最长公共前缀的最
大值。因为任意两个后缀的最长公共前缀都是 height 数组里某一段的最小值,
那么这个值一定不大于 height 数组里的最大值。所以最长重复子串的长度就是
height 数组里的最大值。这个做法的时间复杂度为 O(n)
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
using namespace std;
typedef long long ll;
const int maxn = 200010;
int cntA[maxn],cntB[maxn],sa[maxn],tsa[maxn],A[maxn],B[maxn],height[maxn];
int a[maxn];
int Rank[maxn];
ll n;
int m,k;
int ch[maxn];
void solve()
{
for(int i = 0;i <= m;i++) cntA[i] = 0;
for(int i = 1;i <= n;i++) cntA[ch[i-1]]++;
for(int i = 1;i <= m;i++) cntA[i] += cntA[i-1];
for(int i = n;i;i--) sa[cntA[ch[i-1]]--] = i;
Rank[sa[1]] = 1;
for(int i = 2;i <= n;i++)
{
Rank[sa[i]] = Rank[sa[i-1]];
if(ch[sa[i]-1] != ch[sa[i-1]-1]) Rank[sa[i]]++;
}
for(int l = 1;Rank[sa[n]] < n;l <<= 1)
{
memset(cntA,0,sizeof(cntA));
memset(cntB,0,sizeof(cntB));
for(int i = 1;i <= n;i++)
{
cntA[A[i] = Rank[i]]++;
cntB[B[i] = (i+l <= n)?Rank[i+l]:0]++;
}
for(int i = 1;i <= n;i++) cntB[i] += cntB[i-1];
for(int i = n;i; i--) tsa[cntB[B[i]]--] = i;
for(int i = 1;i <= n;i++) cntA[i] += cntA[i-1];
for(int i = n; i;i--) sa[cntA[A[tsa[i]]]--] = tsa[i];
Rank[sa[1]] = 1;
for(int i = 2;i <= n;i++)
{
Rank[sa[i]] = Rank[sa[i-1]];
if(A[sa[i]] != A[sa[i-1]] || B[sa[i]] != B[sa[i-1]]) Rank[sa[i]]++;
}
}
for(int i = 1,j = 0;i <= n;i++)
{
if(j) j--;
while(ch[i+j-1] == ch[sa[Rank[i]-1] + j - 1]) j++;
height[Rank[i]] = j;
}
}
int ok(int x)
{
int i,ma,mi=sa[1],cnt = 1;
for(int i =1;i <= n;i++)
{
if(height[i] >= x)
{
cnt++;
mi = min(mi,sa[i]);
}
else
{
cnt = 1;
mi = sa[i];
}
if(cnt >= k)
return 1;
}
return 0;
}
int main()
{
scanf("%d%d", &n, &k);
m = 0;
for(int i = 0; i < n; i++)
{
scanf("%d", &ch[i]);
m = max(m,ch[i]);
}
solve();
int l = 1,r = n;
while(l + 1 < r)
{
int mid = (l+r)/ 2;
if(ok(mid))
{
l = mid;
}
else
r = mid;
}
if(ok(r)) printf("%d\n",r);
else printf("%d\n",l);
return 0;
}