前言
最近系統地學習了一下SA,就算法本身並不是十分複雜,但關係到後綴數組的題型卻很多,於是打算整理一下。
計算字符串SA通常有倍增和DC3兩種做法,一般選擇倍增,因爲常數小且寫起來方便一些。
基礎題型
一、重複子串(單個字符串問題)
1. 不可重疊最長重複子串(poj1743)
分析:
題目並不是裸的不可重疊最長重複子串問題,需要對於問題進行一些修正,考慮相鄰兩位之間的差值,再進行負數修正,就可以轉化到這樣的題型。
在後綴數組的很多問題中,height數組具有很好的性質,由於height數組是表示,相鄰排名的後綴的最長前綴長度,而這道題相當於求一對相同的前綴,不過對於前綴有不相交的性質。
首先我們二分答案,如果有一段連續後綴的height都大於,說明這些後綴具有相同的前綴且長度大於,那麼我們只需要這些後綴中差值大於k,顯然有兩個長度大於的子串。
(因爲如果兩個位置的sa差值大於,說明他們之間相隔距離大於,自然前綴不會重疊)
//#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)
分析:
對於任意子串來講,其都可以看做某個後綴的前綴,那麼我們需要統計所有不同前綴的個數,對於後綴來講,對於全部的貢獻爲,同時需要去掉與有交集的個前綴,所以直接統計出答案爲.
//#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的一些典型例題,下面會做一些多字符串的問題。