问题 : 求一个字符串的不可重叠最长重复子串
二分答案 + hash 的思路是比较显然的 , 时间复杂度 O(N∗(logN)2) , 然而这个算法有可能超时。
字符串的每个子串都是后缀的前缀 , 所以后缀蕴含着很大的信息量
这个时候后缀数组就可以派上用场了~
设 sa[i] 为排名为 i 的后缀的起始位置 , rank[i] 为以 i 为起始位置的后缀的排名
求出 sa[] 之后, 可以直接求出 rank[]
首先我们对字符串 S 的后缀排序
直接排序时间复杂度是
利用后缀的性质,我们可以利用倍增的思想排序
假设我们的得到了只考虑后缀最多前 k 位时的排名
那么以
就可以得到考虑后缀最多前
2∗k
位时的排名
(为了使得排序具有稳定的效率 , 一般采用基数排序)
代码实现有一定技巧
时间复杂度: O(N∗logN)
但是只有 sa[] , rank[] 还不够 , 我们还想求出
height[i]
: 排名为
i
的后缀与排名为
可以证明 , height[rank[i]]≥height[rank[i−1]]−1
时间复杂度: O(N)
证明
定义函数 LCP(x , y) 表示 sa[x ~ y] 的后缀的 LCP
LCP(x , y) = min{LCP(x + t, x + t + 1)} (0 <= t < y - x)
设 rank[k] = rank[i - 1] - 1
∴ LCP(rank[k], rank[i - 1]) = height[rank[i - 1]]
∴ LCP(rank[k + 1], rank[i]) >= LCP(rank[k], rank[i - 1]) - 1 = height[rank[i - 1]] - 1
此时如果 rank[k + 1] = rank[i] - 1, 结论显然成立
如果 rank[k + 1] <> rank[i] - 1 , 即 rank[k + 1] < rank[i] - 1
显然有
LCP(rank[i] - 1, rank[i])
>= LCP(rank[k + 1], rank[i])
>= height[rank[i - 1]] - 1
∴ 结论成立
代码解释
n
: 字符串的长度
c[i]
: 关键字的值小于或等于
i
的元素个数
//Suffix Array
const int maxn = 1e5 + 50;
char s[maxn];
int sa[maxn], t0[maxn], t1[maxn], c[maxn], n;
int rank[maxn], height[maxn];
void build_SA(int m)
{
int *x = t0, *y = t1;
for(int i = 0; i < m; i++) c[i] = 0;
for(int i = 0; i < n; i++) c[x[i] = s[i]]++;
for(int i = 1; i < m; i++) c[i] += c[i - 1];
for(int i = n - 1; i >= 0; i--) sa[--c[x[i]]] = i;
for(int k = 1; k <= n; k <<= 1)
{
int p = 0;
for(int i = n - k; i < n; i++) y[p++] = i;
for(int i = 0; i < n; i++) if(sa[i] >= k) y[p++] = sa[i] - k;
for(int i = 0; i < m; i++) c[i] = 0;
for(int i = 0; i < n; i++) c[x[y[i]]]++;
for(int i = 1; i < m; i++) c[i] += c[i-1];
for(int i = n - 1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];
std::swap(x, y);
p = 1, x[sa[0]] = 0;
for(int i = 1; i < n; i++)
x[sa[i]] = y[sa[i-1]] == y[sa[i]] && sa[i-1] + k < n && sa[i] + k < n && y[sa[i-1]+k] == y[sa[i]+k] ? p-1 : p++;
if(p == n) break;
m = p;
}
}
void build_Height()
{
int k = 0;
for(int i = 0; i < n; i++) rank[sa[i]] = i;
for(int i = 0; i < n; i++)
{
if(k != 0) k--;
if(!rank[i]) continue;
int j = sa[rank[i] - 1];
while(s[i + k] == s[j + k]) k++;
height[rank[i]] = k;
}
}
模板提交链接 : uoj #35
回到最开始的问题 , 如何求一个字符串的不可重叠最长重复子串?
先二分答案,把题目变成判定性问题 : 判断是否存在两个长度为 k 的子串, 它们是相同的 , 且不重叠
如果
(同一个集合中的点对 (x,y) 都满足 LCP(x,y)≥k )
然后判断是否存在点对 (x,y) , sa[x] 和 sa[y] 在同一个集合内 且 |sa[x]−sa[y]|≥k
我们只需要对 sa[] 求区间最值即可 , 时间复杂度为 O(N)
总时间复杂度: O(N∗logN)
提交链接 : poj 1743
题意 : 将长度为
n
的序列做差分 , 得到一个长度为
记最长不重叠重复子串长度为
P
, 如果
// poj 1743
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <ctime>
#include <iostream>
#include <algorithm>
template<class Num>void read(Num &x)
{
char c;
int flag = 1;
while((c = getchar()) < '0' || c > '9')
if(c == '-') flag *= -1;
x = c - '0';
while((c = getchar()) >= '0' && c <= '9')
x *= 10, x += c - '0';
x *= flag;
}
template<class Num>void write(Num x)
{
if(!x) {putchar('0'); return;}
static char s[20];
int sl = 0;
if(x < 0) putchar('-'), x = -x;
while(x) s[sl++] = x%10 + '0', x /= 10;
while(sl) putchar(s[--sl]);
}
const int maxn = 2e4 + 20, base = 100;
int n, s[maxn];
int sa[maxn], rank[maxn], t0[maxn], t1[maxn];
int height[maxn], c[maxn];
void build_sa(int m)
{
int *x = t0, *y = t1;
for(int i = 1; i <= m; i++) c[i] = 0;
for(int i = 1; i <= n; i++) c[x[i] = s[i]]++;
for(int i = 1; i <= m; i++) c[i] += c[i - 1];
for(int i = n; i >= 1; i--) sa[c[x[i]]--] = i;
for(int k = 1; k <= n; k <<= 1)
{
int p = 0;
for(int i = 0; i < k; i++) y[++p] = n - i;
for(int i = 1; i <= n; i++)
if(sa[i] > k) y[++p] = sa[i] - k;
for(int i = 1; i <= m; i++) c[i] = 0;
for(int i = 1; i <= n; i++) c[x[y[i]]]++;
for(int i = 1; i <= m; i++) c[i] += c[i - 1];
for(int i = n; i >= 1; i--) sa[c[x[y[i]]]--] = y[i];
std::swap(x, y);
p = 1, x[sa[1]] = 1;
for(int i = 2; i <= n; i++)
x[sa[i]] = y[sa[i]] == y[sa[i - 1]] && sa[i] + k <= n && sa[i - 1] + k <= n && y[sa[i] + k] == y[sa[i - 1] + k] ? p : ++p;
if(p == n) break;
m = p;
}
}
void build_height()
{
int k = 0;
for(int i = 1; i <= n; i++) rank[sa[i]] = i;
for(int i = 1; i <= n; i++)
{
if(k) k--;
if(rank[i] == 1) continue;
int j = sa[rank[i] - 1];
while(s[j + k] == s[i + k]) k++;
height[rank[i]] = k;
}
}
bool check(int k)
{
int e;
for(int i = 1; i <= n; i = e)
{
int min = sa[i], max = sa[i];
e = i + 1;
while(e <= n && height[e] >= k)
{
min = std::min(sa[e], min);
max = std::max(sa[e], max);
e++;
}
if(max - min >= k) return true;
}
return false;
}
int solve()
{
int l = 4, r = n;
if(!check(l)) return -1;
if(check(r)) return r;
while(l + 1 != r)
{
int mid = (l + r) >> 1;
if(check(mid)) l = mid;
else r = mid;
}
return l;
}
void init()
{
for(int i = 1; i <= n; i++) read(s[i]);
for(int i = 1; i < n; i++)
s[i] = s[i + 1] - s[i] + base;
s[n --] = 0;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("1743.in","r",stdin);
freopen("1743.out","w",stdout);
#endif
while(true)
{
read(n);
if(!n) break;
init();
build_sa(256);
build_height();
write(solve() + 1), puts("");
}
#ifndef ONLINE_JUDGE
fclose(stdin);
fclose(stdout);
#endif
return 0;
}