後綴數組(suffix-array)

前言

最近系統地學習了一下SA,就算法本身並不是十分複雜,但關係到後綴數組的題型卻很多,於是打算整理一下。
計算字符串SA通常有倍增和DC3兩種做法,一般選擇倍增,因爲常數小且寫起來方便一些。

基礎題型

一、重複子串(單個字符串問題)

1. 不可重疊最長重複子串(poj1743)

分析:
題目並不是裸的不可重疊最長重複子串問題,需要對於問題進行一些修正,考慮相鄰兩位之間的差值,再進行負數修正,就可以轉化到這樣的題型。
在後綴數組的很多問題中,height數組具有很好的性質,由於height數組是表示,相鄰排名的後綴的最長前綴長度,而這道題相當於求一對相同的前綴,不過對於前綴有不相交的性質。
首先我們二分答案kk,如果有一段連續後綴的height都大於kk,說明這些後綴具有相同的前綴且長度大於kk,那麼我們只需要這些後綴中sasa差值大於k,顯然有兩個長度大於kk的子串。
(因爲如果兩個位置的sa差值大於kk,說明他們之間相隔距離大於kk,自然前綴不會重疊)

//#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)

分析:
對於任意子串來講,其都可以看做某個後綴的前綴,那麼我們需要統計所有不同前綴的個數,對於後綴suffix(sa[i])suffix(sa[i])來講,對於全部的貢獻爲nsa[i]+1n-sa[i]+1,同時需要去掉與sa[i1]sa[i - 1]有交集的height[i]height[i]個前綴,所以直接統計出答案爲n(n+1)/2Σ(height[i])n*(n+1)/2-\Sigma(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的一些典型例題,下面會做一些多字符串的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章