后缀数组(suffix-array)

前言

最近系统地学习了一下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 nsa[i]+1,同时需要去掉与 s a [ i − 1 ] sa[i - 1] sa[i1]有交集的 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的一些典型例题,下面会做一些多字符串的问题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值