前言
最近系统地学习了一下SA,就算法本身并不是十分复杂,但关系到后缀数组的题型却很多,于是打算整理一下。
计算字符串SA通常有倍增和DC3两种做法,一般选择倍增,因为常数小且写起来方便一些。
基础题型
一、重复子串(单个字符串问题)
1. 不可重叠最长重复子串(poj1743)
分析:
题目并不是裸的不可重叠最长重复子串问题,需要对于问题进行一些修正,考虑相邻两位之间的差值,再进行负数修正,就可以转化到这样的题型。
在后缀数组的很多问题中,height数组具有很好的性质,由于height数组是表示,相邻排名的后缀的最长前缀长度,而这道题相当于求一对相同的前缀,不过对于前缀有不相交的性质。
首先我们二分答案
k
k
k,如果有一段连续后缀的height都大于
k
k
k,说明这些后缀具有相同的前缀且长度大于
k
k
k,那么我们只需要这些后缀中
s
a
sa
sa差值大于k,显然有两个长度大于
k
k
k的子串。
(因为如果两个位置的sa差值大于
k
k
k,说明他们之间相隔距离大于
k
k
k,自然前缀不会重叠)
//#include <algorithm>
//#include <iostream>
//#include <cstdio>
//#include <cstring>
//#include <cmath>
//#include <cstdlib>
//#include <cctype>
#include <bits/stdc++.h>
#define rep( i , l , r ) for( int i = (l) ; i <= (r) ; ++i )
#define per( i , r , l ) for( int i = (r) ; i >= (l) ; --i )
#define erep( i , u ) for( int i = head[(u)] ; ~i ; i = e[i].nxt )
using namespace std;
int _read(){
char ch = getchar();
int x = 0 , f = 1 ;
while( ch < '0' || ch > '9' )
if( ch == '-' ) f = -1 , ch = getchar();
else ch = getchar();
while( '0' <= ch && ch <= '9' )
x = (ch - '0') + (x << 3) + (x << 1) , ch = getchar();
return x * f;
}
const int maxn = 22222;
int wa[maxn] , wb[maxn] , wv[maxn] , _sum[maxn];
int cmp( int *r , int a , int b , int l ){
return r[a] == r[b] && r[a+l] == r[b+l];
}
void da( int *r , int *sa , int n , int m ){
// n the length of the string
// m the character the string include( such 127 )
//int m = 127;
int i , j , p , *x = wa , *y = wb , *t;
// radix-sort
for( i = 0 ; i < m ; ++i ) _sum[i] = 0;
for( i = 0 ; i < n ; ++i ) _sum[x[i] = r[i]]++;
for( i = 1 ; i < m ; ++i ) _sum[i] += _sum[i - 1];
for( i = n - 1 ; i >= 0 ; --i ) sa[--_sum[x[i]]] = i;
//calc sa
for( j = 1 , p = 1 ; p < n ; j *= 2 , m = p ){
for( p = 0 , i = n - j ; i < n ; ++i ) y[p++] = i;
for( i = 0 ; i < n ; ++i ) if( sa[i] >= j ) y[p++] = sa[i] - j;
for( i = 0 ; i < n ; ++i ) wv[i] = x[y[i]];
for( i = 0 ; i < m ; ++i ) _sum[i] = 0;
for( i = 0 ; i < n ; ++i ) _sum[wv[i]]++;
for( i = 1 ; i < m ; ++i ) _sum[i] += _sum[i - 1];
for( i = n - 1 ; i >= 0 ; --i ) sa[--_sum[wv[i]]] = y[i];
for( t = x , x = y , y = t , p = 1 , x[sa[0]] = 0 , i = 1 ; i < n ; i++ )
x[sa[i]] = cmp(y , sa[i - 1] , sa[i] , j) ? p - 1 : p++;
}
return;
}
int rank[maxn] , height[maxn];
void calch( int *r , int *sa , int n ){
int i , j , k = 0;
for( i = 1 ; i <= n ; ++i ) rank[sa[i]] = i;
for( i = 0 ; i < n ; height[rank[i++]] = k ){
for( k ? k-- : 0 , j = sa[rank[i] - 1] ; r[i + k] == r[j + k] ; ++k );
}
return;
}
bool check( int n , int *sa , int k ){
int mi = sa[1] , mx = sa[1];
for( int i = 2 ; i <= n ; ++i ){
if( height[i] < k ){
mi = mx = sa[i];
}else{
if( sa[i] > mx ) mx = sa[i];
if( sa[i] < mi ) mi = sa[i];
if( mx - mi > k ) return 1;
}
}
return 0;
}
int r[maxn] , sa[maxn];
int main(){
int N = 0;
ios::sync_with_stdio(false);
while( cin >> N && N ){
int x , y;
cin >> x;
--N;
for( int i = 0 ; i < N ; ++i ){
cin >> y;
r[i] = y - x + 100;
x = y;
}
r[N] = 0;
da( r , sa , N + 1 , 200 );
calch( r , sa , N );
int l = 1 , r = N;
while( l <= r ){
int mid = (l + r) >> 1;
if( check(N, sa , mid) ) l = mid + 1;
else r = mid - 1;
}
if( r >= 4 ) cout << r + 1 << endl;
else cout << 0 << endl;
}
return 0;
}
此题作为SA的模板题
2. 可重叠的k次最长重复子串(poj3261)
分析:
这个和上面一题做法相似,同样是二分答案ans,每次判断是否存在k个连续后缀满足height值不小于ans。
3. 不相同的子串的个数(spoj694)
分析:
对于任意子串来讲,其都可以看做某个后缀的前缀,那么我们需要统计所有不同前缀的个数,对于后缀
s
u
f
f
i
x
(
s
a
[
i
]
)
suffix(sa[i])
suffix(sa[i])来讲,对于全部的贡献为
n
−
s
a
[
i
]
+
1
n-sa[i]+1
n−sa[i]+1,同时需要去掉与
s
a
[
i
−
1
]
sa[i - 1]
sa[i−1]有交集的
h
e
i
g
h
t
[
i
]
height[i]
height[i]个前缀,所以直接统计出答案为
n
∗
(
n
+
1
)
/
2
−
Σ
(
h
e
i
g
h
t
[
i
]
)
n*(n+1)/2-\Sigma(height[i])
n∗(n+1)/2−Σ(height[i]).
//#include <algorithm>
//#include <iostream>
//#include <cstdio>
//#include <cstring>
//#include <cmath>
//#include <cstdlib>
//#include <cctype>
#include <bits/stdc++.h>
#define rep( i , l , r ) for( int i = (l) ; i <= (r) ; ++i )
#define per( i , r , l ) for( int i = (r) ; i >= (l) ; --i )
#define erep( i , u ) for( int i = head[(u)] ; ~i ; i = e[i].nxt )
using namespace std;
int _read(){
char ch = getchar();
int x = 0 , f = 1 ;
while( ch < '0' || ch > '9' )
if( ch == '-' ) f = -1 , ch = getchar();
else ch = getchar();
while( '0' <= ch && ch <= '9' )
x = (ch - '0') + (x << 3) + (x << 1) , ch = getchar();
return x * f;
}
const int maxn = 55000;
int wa[maxn] , wb[maxn] , wv[maxn] , _sum[maxn];
int cmp( int *r , int a , int b , int l ){
return r[a] == r[b] && r[a+l] == r[b+l];
}
void da( int *r , int *sa , int n , int m ){
// n the length of the string
// m the character the string include( such 127 )
//int m = 127;
int i , j , p , *x = wa , *y = wb , *t;
// radix-sort
for( i = 0 ; i < m ; ++i ) _sum[i] = 0;
for( i = 0 ; i < n ; ++i ) _sum[x[i] = r[i]]++;
for( i = 1 ; i < m ; ++i ) _sum[i] += _sum[i - 1];
for( i = n - 1 ; i >= 0 ; --i ) sa[--_sum[x[i]]] = i;
//calc sa
for( j = 1 , p = 1 ; p < n ; j *= 2 , m = p ){
for( p = 0 , i = n - j ; i < n ; ++i ) y[p++] = i;
for( i = 0 ; i < n ; ++i ) if( sa[i] >= j ) y[p++] = sa[i] - j;
for( i = 0 ; i < n ; ++i ) wv[i] = x[y[i]];
for( i = 0 ; i < m ; ++i ) _sum[i] = 0;
for( i = 0 ; i < n ; ++i ) _sum[wv[i]]++;
for( i = 1 ; i < m ; ++i ) _sum[i] += _sum[i - 1];
for( i = n - 1 ; i >= 0 ; --i ) sa[--_sum[wv[i]]] = y[i];
for( t = x , x = y , y = t , p = 1 , x[sa[0]] = 0 , i = 1 ; i < n ; i++ )
x[sa[i]] = cmp(y , sa[i - 1] , sa[i] , j) ? p - 1 : p++;
}
return;
}
int rk[maxn] , height[maxn];
void calch( int *r , int *sa , int n ){
int i , j , k = 0;
for( i = 1 ; i <= n ; ++i ) rk[sa[i]] = i;
for( i = 0 ; i < n ; height[rk[i++]] = k ){
for( k ? k-- : 0 , j = sa[rk[i] - 1] ; r[i + k] == r[j + k] ; ++k );
}
return;
}
int r[maxn] , sa[maxn];
typedef long long ll;
int main(){
int T = 0;
cin >> T;
string s;
// cout << T << endl;
while( T-- ){
cin >> s;
// cout << s << endl;
for( int i = 0 ; i < s.length() ; ++i ) r[i] = (int)s[i];
int n = s.length();
da( r , sa , n + 1 , 128 );
calch( r , sa , n );
int ans = n * (n + 1) / 2;
for( int i = 1 ; i <= n ; ++i ) {
ans -= height[i];
}
cout << ans << endl;
}
return 0;
}
以上是关于单个字符串有关SA的一些典型例题,下面会做一些多字符串的问题。