水题
POJ2774
求两个串的最长公共子串(最长公共前缀)。
求两个数的最长公共子串,可以将两个字符串中间加一个‘$’然后拼接成一个子串。
这样两个最长公共子串问题转换为 一个大串的最长公共前缀,因为height数组存的是 i-1 和 i位置的最长公共前缀,因此只要保证 i-1 和 i 位置的子串是不同的字符串即可。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cstdlib>
const int maxn = 1000005;
using namespace std;
int t1[maxn],t2[maxn],c[maxn];
//求sa数组需要的中间变量,不需要赋值
//带排序的字符串放在s数组中,从s[0] - s[n-1],
//长度为n,且最大值小于m
//除了s[n-1]之外的所有s[i] 都大于0,r[n-1] = 0
//函数结束后,结果放在sa数组中
bool cmp(int *r,int a,int b,int l){
return r[a] == r[b] && r[a+l] == r[b+l];
}
void da(int str[],int sa[],int rank[],int height[],int n,int m){
n++;
int i,j,p,*x = t1,*y = t2;
for(i = 0;i< m ;i ++) c[i] = 0;
for(i=0;i<n;i++) c[x[i] = str[i] ]++;
for(i=1;i<m;i++) c[i] += c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[i] ]] = i;
for(j=1;j<=n;j<<=1){
p = 0;
//直接利用sa数组排序第二关键字
for(i=n-j;i<n;i++) y[p++] = i;
for(i=0;i<n;i++) if(sa[i] >= j) y[p++] = sa[i] - j;
//这样数组y保存的就是按照第二关键字排序的结果
//基数排序第一关键字
for(i=0;i<m;i++) c[i] = 0;
for(i=0;i<n;i++) c[x[y[i]] ]++;
for(i=1;i<m;i++) c[i] += c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[y[i] ]] ] = y[i];
//根据sa和x数组计算新的x数组
swap(x,y);
p = 1;x[ sa[0]] = 0;
for(i=1;i<n;i++)
x[sa[i] ] = cmp(y,sa[i-1],sa[i],j) ? p-1:p++;
if(p >= n) break;
m = p; //下次基数排序的最大值
}
int k = 0;
n--;
for(i=0;i<=n;i++) rank[sa[i] ] = i;
for(i=0;i<n;i++){
if(k) k--;
j = sa[rank[i] - 1 ];
while(str[i+k] == str[j+k] ) k++;
height[rank[i] ] = k;
}
}
int rank[maxn], height[maxn];
int RMQ[maxn];
int mm[maxn];
int best[20][maxn];
void initRMQ(int n){
mm[0] = -1;
for(int i=1;i<=n;i++)
mm[i] = ((i & (i-1))==0 ) ? mm[i-1] +1 : mm[i-1];
for(int i=1;i<=n;i++) best[0][i] = i;
for(int i=1;i<=mm[n];i++)
for(int j=1;j+(1<<i)-1 <=n;j++){
int a = best[i-1][j];
int b = best[i-1][j+(1<<(i-1)) ];
if(RMQ[a] < RMQ[b]) best[i][j] = a;
else best[i][j] = b;
}
}
int askRMQ(int a,int b){
int t;
t = mm[b-a+1];
b-=(1<<t)-1;
a = best[t][a] ; b = best[t][b];
return RMQ[a] < RMQ[b] ? a:b;
}
int lcp(int a,int b){
a = rank[a]; b = rank[b];
if(a > b) swap(a,b);
return height[askRMQ(a+1,b)];
}
char str[maxn];
int r[maxn];
int sa[maxn];
char s1[maxn],s2[maxn];
int main(){
gets(s1);
gets(s2);
int len = strlen(s1);
strcat(s1,"$");
strcat(s1,s2);
int n = strlen(s1), m = 0;
for(int i=0;i<n;i++){
m = max(m, (int)s1[i]);
r[i] = s1[i];
}
r[n] = 0;
da(r,sa,rank,height,n,m+1);
//cout<<"gg"<<endl;
// sa 存的是 后缀从小到大排序后的位置
// sa[i]=j,则 rank[j]=i。互逆
// height[i]=suffix(sa[i-1])和 suffix(sa[i])的最长公共前缀
int res = 0;
for(int i=1;i<=n;i++){
//不属于同一个字符串,
if( (sa[i-1] < len && sa[i] >=len) ||
(sa[i-1] >= len && sa[i] < len) )
res = max(res,height[i]);
}
printf("%d\n",res);
}
POJ1743
首先处理数据,将i 和 i-1位置的差值构造新的数组。
然后只要求新数组 最长的不重叠且重复出现的字串的长度。
在我们假设-最长,不重叠,重复-子串的长度为len,那么在一个height数组中有一些hgt[i]会小于len,在这个i左右的两个子串,他们LCP是不可能大于或等于len的,这样,就可以吧height数组看做很多LCP >= len的段,我们在每一段中进行扫描,记录这一段中最大和最小的子串串索引(sa[x]),如果两者之和小于len,说明重叠了,否则就找到了一个可行解。
/*
* POJ 1743 Musical Theme
* 有N(1 <= N <=20000)个音符的序列来表示一首乐曲,每个音符都是1..88范围内的整数,现在要找一个重复的主题。
* “主题”是整个音符序列的一个子串,它需要满足如下条件:
* 1.长度至少为5个音符
* 2.在乐曲中重复出现(可能经过转调,“转调”的意思是主题序列中每个音符都被加上或减去了同一个整数值。)
* 3.重复出现的同一主题不能有公共部分。
*
* 先转化成相邻两项的差值,然后就是找不可重叠重复子串。
* 做法就是二分答案LEN
* 然后根据height值进行分组
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cstdlib>
const int maxn = 20000 +10;
using namespace std;
int sa[maxn];
int rank[maxn], height[maxn];
int t1[maxn],t2[maxn],c[maxn];
//求sa数组需要的中间变量,不需要赋值
//带排序的字符串放在s数组中,从s[0] - s[n-1],
//长度为n,且最大值小于m
//除了s[n-1]之外的所有s[i] 都大于0,r[n-1] = 0
//函数结束后,结果放在sa数组中
bool cmp(int *r,int a,int b,int l){
return r[a] == r[b] && r[a+l] == r[b+l];
}
void da(int str[],int sa[],int rank[],int height[],int n,int m){
n++;
int i,j,p,*x = t1,*y = t2;
for(i = 0;i< m ;i ++) c[i] = 0;
for(i=0;i<n;i++) c[x[i] = str[i] ]++;
for(i=1;i<m;i++) c[i] += c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[i] ]] = i;
for(j=1;j<=n;j<<=1){
p = 0;
//直接利用sa数组排序第二关键字
for(i=n-j;i<n;i++) y[p++] = i;
for(i=0;i<n;i++) if(sa[i] >= j) y[p++] = sa[i] - j;
//这样数组y保存的就是按照第二关键字排序的结果
//基数排序第一关键字
for(i=0;i<m;i++) c[i] = 0;
for(i=0;i<n;i++) c[x[y[i]] ]++;
for(i=1;i<m;i++) c[i] += c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[y[i] ]] ] = y[i];
//根据sa和x数组计算新的x数组
swap(x,y);
p = 1;x[ sa[0]] = 0;
for(i=1;i<n;i++)
x[sa[i] ] = cmp(y,sa[i-1],sa[i],j) ? p-1:p++;
if(p >= n) break;
m = p; //下次基数排序的最大值
}
int k = 0;
n--;
for(i=0;i<=n;i++) rank[sa[i] ] = i;
for(i=0;i<n;i++){
if(k) k--;
j = sa[rank[i] - 1 ];
while(str[i+k] == str[j+k] ) k++;
height[rank[i] ] = k;
}
}
int s[maxn];
bool check(int n,int k)
{
int Max=sa[1],Min=sa[1];
for(int i=2;i<=n;i++)
{
if(height[i]<k)Max=Min=sa[i];
else
{
if(sa[i]<Min)Min=sa[i];
if(sa[i]>Max)Max=sa[i];
if(Max-Min>=k) return true;
}
}
return false;
}
int main(){
int n;
while(scanf("%d",&n) == 1 && n){
for(int i=0;i<n;i++) scanf("%d",&s[i]);
for(int i=n-1;i>0;i--) s[i] = s[i] - s[i-1] + 90;
n--; //减少一个长度
for(int i=0;i<n;i++) s[i] = s[i+1];
s[n] = 0;
da(s,sa,rank,height,n,200);
int ans = -1;
int l = 1, r = n/2;
while( l<= r){
int mid = (l + r) /2;
if(check(n,mid)){
ans = mid;
l = mid+1;
}else
r = mid-1;
}
if(ans < 4) printf("0\n");
else printf("%d\n",ans+1);
}
return 0;
}
POJ3261
可重叠的K次(大于等于K)最长重复子串
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cstdlib>
const int maxn = 1000005;
using namespace std;
int sa[maxn];
int rank[maxn], height[maxn];
int t1[maxn],t2[maxn],c[maxn];
//求sa数组需要的中间变量,不需要赋值
//带排序的字符串放在s数组中,从s[0] - s[n-1],
//长度为n,且最大值小于m
//除了s[n-1]之外的所有s[i] 都大于0,r[n-1] = 0
//函数结束后,结果放在sa数组中
bool cmp(int *r,int a,int b,int l){
return r[a] == r[b] && r[a+l] == r[b+l];
}
void da(int str[],int sa[],int rank[],int height[],int n,int m){
n++;
int i,j,p,*x = t1,*y = t2;
for(i = 0;i< m ;i ++) c[i] = 0;
for(i=0;i<n;i++) c[x[i] = str[i] ]++;
for(i=1;i<m;i++) c[i] += c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[i] ]] = i;
for(j=1;j<=n;j<<=1){
p = 0;
//直接利用sa数组排序第二关键字
for(i=n-j;i<n;i++) y[p++] = i;
for(i=0;i<n;i++) if(sa[i] >= j) y[p++] = sa[i] - j;
//这样数组y保存的就是按照第二关键字排序的结果
//基数排序第一关键字
for(i=0;i<m;i++) c[i] = 0;
for(i=0;i<n;i++) c[x[y[i]] ]++;
for(i=1;i<m;i++) c[i] += c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[y[i] ]] ] = y[i];
//根据sa和x数组计算新的x数组
swap(x,y);
p = 1;x[ sa[0]] = 0;
for(i=1;i<n;i++)
x[sa[i] ] = cmp(y,sa[i-1],sa[i],j) ? p-1:p++;
if(p >= n) break;
m = p; //下次基数排序的最大值
}
int k = 0;
n--;
for(i=0;i<=n;i++) rank[sa[i] ] = i;
for(i=0;i<n;i++){
if(k) k--;
j = sa[rank[i] - 1 ];
while(str[i+k] == str[j+k] ) k++;
height[rank[i] ] = k;
}
}
int RMQ[maxn];
int mm[maxn];
int best[20][maxn];
void initRMQ(int n){
mm[0] = -1;
for(int i=1;i<=n;i++)
mm[i] = ((i & (i-1))==0 ) ? mm[i-1] +1 : mm[i-1];
for(int i=1;i<=n;i++) best[0][i] = i;
for(int i=1;i<=mm[n];i++)
for(int j=1;j+(1<<i)-1 <=n;j++){
int a = best[i-1][j];
int b = best[i-1][j+(1<<(i-1)) ];
if(RMQ[a] < RMQ[b]) best[i][j] = a;
else best[i][j] = b;
}
}
int askRMQ(int a,int b){
int t;
t = mm[b-a+1];
b-=(1<<t)-1;
a = best[t][a] ; b = best[t][b];
return RMQ[a] < RMQ[b] ? a:b;
}
int lcp(int a,int b){
a = rank[a]; b = rank[b];
if(a > b) swap(a,b);
return height[askRMQ(a+1,b)];
}
int arr[20010];
int n,m;
bool fun(int k){
int cnt = 1;
for(int i=2;i<=n;i++){
if(height[i] >= k){
cnt ++;
}else{
cnt = 1;
}
if(cnt >= m)
return true;
}
return false;
}
int main(){
while(scanf("%d%d",&n,&m) !=EOF){
int maxx = -1;
for(int i=0;i<n;i++){
scanf("%d",&arr[i]);
maxx = max(maxx,arr[i]);
}
da(arr,sa,rank,height,n,maxx+1);
//二分长度
int l = 1,r = n;
maxx = 0;
while(l <= r){
int mid = (l + r) >>1;
if(fun(mid)){
maxx = mid;
l = mid + 1;
}else{
r = mid - 1;
}
}
printf("%d\n",maxx);
}
}
SPOJ694 不同字串个数
每一个子串一定是某个后缀的前缀,那么问题便等价于求所有后缀之间的不相同的前缀个数。我们按sa的顺序来考虑,当加入sa[k]的时候,sa[k]这个后缀的长度为n-sa[k],那么便有n-sa[k]个前缀,但是由heigh数组可知sa[k]与sa[k-1]有height[k]个前缀是相同的,所以要除去,最终的答案便是sigma(n-sa[k]+height[k])
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cstdlib>
const int maxn = 50500;
using namespace std;
int sa[maxn];
int rank[maxn], height[maxn];
int t1[maxn],t2[maxn],c[maxn];
//求sa数组需要的中间变量,不需要赋值
//带排序的字符串放在s数组中,从s[0] - s[n-1],
//长度为n,且最大值小于m
//除了s[n-1]之外的所有s[i] 都大于0,r[n-1] = 0
//函数结束后,结果放在sa数组中
bool cmp(int *r,int a,int b,int l){
return r[a] == r[b] && r[a+l] == r[b+l];
}
void da(char str[],int sa[],int rank[],int height[],int n,int m){
n++;
int i,j,p,*x = t1,*y = t2;
for(i = 0;i< m ;i ++) c[i] = 0;
for(i=0;i<n;i++) c[x[i] = str[i] ]++;
for(i=1;i<m;i++) c[i] += c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[i] ]] = i;
for(j=1;j<=n;j<<=1){
p = 0;
//直接利用sa数组排序第二关键字
for(i=n-j;i<n;i++) y[p++] = i;
for(i=0;i<n;i++) if(sa[i] >= j) y[p++] = sa[i] - j;
//这样数组y保存的就是按照第二关键字排序的结果
//基数排序第一关键字
for(i=0;i<m;i++) c[i] = 0;
for(i=0;i<n;i++) c[x[y[i]] ]++;
for(i=1;i<m;i++) c[i] += c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[y[i] ]] ] = y[i];
//根据sa和x数组计算新的x数组
swap(x,y);
p = 1;x[ sa[0]] = 0;
for(i=1;i<n;i++)
x[sa[i] ] = cmp(y,sa[i-1],sa[i],j) ? p-1:p++;
if(p >= n) break;
m = p; //下次基数排序的最大值
}
int k = 0;
n--;
for(i=0;i<=n;i++) rank[sa[i] ] = i;
for(i=0;i<n;i++){
if(k) k--;
j = sa[rank[i] - 1 ];
while(str[i+k] == str[j+k] ) k++;
height[rank[i] ] = k;
}
}
int solve(int n){
int sum=0;
for(int i=1;i<=n;i++)
sum+=n-sa[i]-height[i];
return sum;
}
char str[50500];
int s[50500];
int main(){
while(scanf("%s",str) !=EOF){
int len = strlen(str);
da(str,sa,rank,height,len,130);
printf("%d\n",solve(len));
}
}
给定一个字符串,求最长回文子串。
算法分析:
穷举每一位,然后计算以这个字符为中心的最长回文子串。注意这里要分两
种情况,一是回文子串的长度为奇数,二是长度为偶数。两种情况都可以转化为
求一个后缀和一个反过来写的后缀的最长公共前缀。具体的做法是:将整个字符
串反过来写在原字符串后面,中间用一个特殊的字符隔开。这样就把问题变为了
求这个新的字符串的某两个后缀的最长公共前缀。(论文)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cstdlib>
const int maxn = 2020;
using namespace std;
int sa[maxn];
int rank[maxn], height[maxn];
int t1[maxn],t2[maxn],c[maxn];
//求sa数组需要的中间变量,不需要赋值
//带排序的字符串放在s数组中,从s[0] - s[n-1],
//长度为n,且最大值小于m
//除了s[n-1]之外的所有s[i] 都大于0,r[n-1] = 0
//函数结束后,结果放在sa数组中
bool cmp(int *r,int a,int b,int l){
return r[a] == r[b] && r[a+l] == r[b+l];
}
void da(int str[],int sa[],int rank[],int height[],int n,int m){
n++;
int i,j,p,*x = t1,*y = t2;
for(i = 0;i< m ;i ++) c[i] = 0;
for(i=0;i<n;i++) c[x[i] = str[i] ]++;
for(i=1;i<m;i++) c[i] += c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[i] ]] = i;
for(j=1;j<=n;j<<=1){
p = 0;
//直接利用sa数组排序第二关键字
for(i=n-j;i<n;i++) y[p++] = i;
for(i=0;i<n;i++) if(sa[i] >= j) y[p++] = sa[i] - j;
//这样数组y保存的就是按照第二关键字排序的结果
//基数排序第一关键字
for(i=0;i<m;i++) c[i] = 0;
for(i=0;i<n;i++) c[x[y[i]] ]++;
for(i=1;i<m;i++) c[i] += c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[y[i] ]] ] = y[i];
//根据sa和x数组计算新的x数组
swap(x,y);
p = 1;x[ sa[0]] = 0;
for(i=1;i<n;i++)
x[sa[i] ] = cmp(y,sa[i-1],sa[i],j) ? p-1:p++;
if(p >= n) break;
m = p; //下次基数排序的最大值
}
int k = 0;
n--;
for(i=0;i<=n;i++) rank[sa[i] ] = i;
for(i=0;i<n;i++){
if(k) k--;
j = sa[rank[i] - 1 ];
while(str[i+k] == str[j+k] ) k++;
height[rank[i] ] = k;
}
}
int RMQ[maxn];
int mm[maxn];
int best[20][maxn];
void initRMQ(int n){
mm[0] = -1;
for(int i=1;i<=n;i++)
mm[i] = ((i & (i-1))==0 ) ? mm[i-1] +1 : mm[i-1];
for(int i=1;i<=n;i++) best[0][i] = i;
for(int i=1;i<=mm[n];i++)
for(int j=1;j+(1<<i)-1 <=n;j++){
int a = best[i-1][j];
int b = best[i-1][j+(1<<(i-1)) ];
if(RMQ[a] < RMQ[b]) best[i][j] = a;
else best[i][j] = b;
}
}
int askRMQ(int a,int b){
int t;
t = mm[b-a+1];
b-=(1<<t)-1;
a = best[t][a] ; b = best[t][b];
return RMQ[a] < RMQ[b] ? a:b;
}
int lcp(int a,int b){
a = rank[a]; b = rank[b];
if(a > b) swap(a,b);
return height[askRMQ(a+1,b)];
}
int r[maxn];
char str[maxn];
int main(){
while(scanf("%s",str)!=EOF){
int len = strlen(str);
int n = 2 * len + 1;
for(int i=0;i<len;i++) r[i] = str[i];
r[len] = 1; // 不同字符
for(int i=0;i<len;i++) r[i + len +1] = str[len-1-i];
r[n] = 0;
da(r,sa,rank,height,n,128);
for(int i=1;i<=n;i++) RMQ[i] = height[i];
initRMQ(n);
int ans = 0,st;
int tmp;
for(int i=0;i<len;i++){
tmp = lcp(i,n-i); //偶数
if(2 * tmp > ans){
ans = 2*tmp;
st = i - tmp;
}
tmp = lcp(i,n-i-1); // 奇数
if(2 * tmp -1 > ans){
ans = 2 * tmp -1;
st = i - tmp + 1;
}
}
str[st+ans] = 0;
printf("%s\n",str+st);
}
return 0;
}
POJ2406 寻找循环节,KMP可做,后缀数组需要da3构造,否则超时
重复次数最多的连续重复子串(spoj687,pku3693)
这题目是对height数组进行RMQ稍微改了下模板。将RMQ数组换成了height数组
cx_love题解
在后缀数组神文中也这题的题解。
比较容易理解的部分就是枚举长度为L,然后看长度为L的字符串最多连续出现几次。
既然长度为L的串重复出现,那么str[0],str[l],str[2*l]……中肯定有两个连续的出现在字符串中。
那么就枚举连续的两个,然后从这两个字符前后匹配,看最多能匹配多远。
即以str[i*l],str[i*l+l]前后匹配,这里是通过查询suffix(i*l),suffix(i*l+l)的最长公共前缀
通过rank值能找到i*l,与i*l+l的排名,我们要查询的是这段区间的height的最小值,通过RMQ预处理
达到查询为0(1)的复杂度,
设LCP长度为M, 则答案显然为M / L + 1, 但这不一定是最好的, 因为答案的首尾不一定再我们枚举的位置上. 我的解决方法是, 我们考虑M % L的值的意义, 我们可以认为是后面多了M % L个字符, 但是我们更可以想成前面少了(L - M % L)个字符! 所以我们求后缀j * L - (L - M % L)与后缀(j + 1) * L - (L - M % L)的最长公共前缀。
即把之前的区间前缀L-M%L即可。
然后把可能取到最大值的长度L保存,由于 题目要求字典序最小,通过sa数组进行枚举,取到的第一组,肯定是字典序最小的。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cstdlib>
const int maxn = 100000 + 10;
using namespace std;
int sa[maxn];
int rank[maxn], height[maxn];
int t1[maxn],t2[maxn],c[maxn];
//求sa数组需要的中间变量,不需要赋值
//带排序的字符串放在s数组中,从s[0] - s[n-1],
//长度为n,且最大值小于m
//除了s[n-1]之外的所有s[i] 都大于0,r[n-1] = 0
//函数结束后,结果放在sa数组中
bool cmp(int *r,int a,int b,int l){
return r[a] == r[b] && r[a+l] == r[b+l];
}
void da(int str[],int sa[],int rank[],int height[],int n,int m){
n++;
int i,j,p,*x = t1,*y = t2;
for(i = 0;i< m ;i ++) c[i] = 0;
for(i=0;i<n;i++) c[x[i] = str[i] ]++;
for(i=1;i<m;i++) c[i] += c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[i] ]] = i;
for(j=1;j<=n;j<<=1){
p = 0;
//直接利用sa数组排序第二关键字
for(i=n-j;i<n;i++) y[p++] = i;
for(i=0;i<n;i++) if(sa[i] >= j) y[p++] = sa[i] - j;
//这样数组y保存的就是按照第二关键字排序的结果
//基数排序第一关键字
for(i=0;i<m;i++) c[i] = 0;
for(i=0;i<n;i++) c[x[y[i]] ]++;
for(i=1;i<m;i++) c[i] += c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[y[i] ]] ] = y[i];
//根据sa和x数组计算新的x数组
swap(x,y);
p = 1;x[ sa[0]] = 0;
for(i=1;i<n;i++)
x[sa[i] ] = cmp(y,sa[i-1],sa[i],j) ? p-1:p++;
if(p >= n) break;
m = p; //下次基数排序的最大值
}
int k = 0;
n--;
for(i=0;i<=n;i++) rank[sa[i] ] = i;
for(i=0;i<n;i++){
if(k) k--;
j = sa[rank[i] - 1 ];
while(str[i+k] == str[j+k] ) k++;
height[rank[i] ] = k;
}
}
int RMQ[maxn];
int mm[maxn];
int best[20][maxn];
void initRMQ(int n){
mm[0] = -1;
for(int i=1;i<=n;i++)
mm[i] = ((i & (i-1))==0 ) ? mm[i-1] +1 : mm[i-1];
for(int i=1;i<=n;i++) best[0][i] = i;
for(int i=1;i<=mm[n];i++)
for(int j=1;j+(1<<i)-1 <=n;j++){
int a = best[i-1][j];
int b = best[i-1][j+(1<<(i-1)) ];
if(height[a] < height[b]) best[i][j] = a;
else best[i][j] = b;
}
}
int askRMQ(int a,int b){
int t;
t = mm[b-a+1];
b-=(1<<t)-1;
a = best[t][a] ; b = best[t][b];
return height[a] < height[b] ? a:b;
}
int lcp(int a,int b){
a = rank[a]; b = rank[b];
if(a > b) swap(a,b);
return height[askRMQ(a+1,b)];
}
char str[maxn];
int r[maxn];
int a[maxn];
int main(){
int cas= 0;
while(scanf("%s",str) == 1){
if(str[0] == '#') break;
cas++;
int n = strlen(str);
for(int i=0;i<=n;i++) r[i] = str[i];
da(r,sa,rank,height,n,128);
initRMQ(n);
int cnt = 0, maxx = 0;
for(int l=1;l<n;l++){
for(int i=0;i+l < n ; i+= l){
int t1 = lcp(i,i+l);
int step = t1/l +1;
int k = i - (l - t1 % l);
if(k >= 0 && t1 % l){
if(lcp(k,k+l) >= t1) step++;
}
if(step > maxx){
maxx = step;
cnt = 0;
a[cnt++] = l;
}else if(step == maxx)
a[cnt++] = l;
}
}
int len = -1,st;
for(int i=1;i<=n&&len == -1 ; i++){
for(int j=0;j<cnt;j++){
int l = a[j];
if(lcp(sa[i],sa[i] + l ) >= (maxx-1)*l ){
len = l;
st = sa[i];
break;
}
}
}
// cout<<len<<" "<<maxx<<endl;
str[st + len*maxx] = 0;
printf("Case %d: %s\n",cas,str+st);
}
return 0;
}
这类问题的一个常用做法是,先连接这两个字符串,然后求后缀数组和
height 数组,再利用 height 数组进行求解。
POJ3415
长度不小于 k 的公共子串的个数(pku3415)
给定两个字符串 A 和 B,求长度不小于 k 的公共子串的个数(可以相同)。
样例 1:
A=“xx”,B=“xx”,k=1,长度不小于 k 的公共子串的个数是 5。
样例 2:
A=“aababaa”,B=“abaabaa”,k=2,长度不小于 k 的公共子串的个数是22。
算法分析:
基本思路是计算 A 的所有后缀和 B 的所有后缀之间的最长公共前缀的长度,
把最长公共前缀长度不小于 k 的部分全部加起来。先将两个字符串连起来,中间
用一个没有出现过的字符隔开。按 height 值分组后,接下来的工作便是快速的
统计每组中后缀之间的最长公共前缀之和。扫描一遍,每遇到一个 B 的后缀就统
计与前面的 A 的后缀能产生多少个长度不小于 k 的公共子串,这里 A 的后缀需要
用一个单调的栈来高效的维护。然后对 A 也这样做一次。具体的细节留给读者思
考。
/*
* POJ 3415 Common Substrings
* 给定两个字符串A和B,求长度不小于k的公共子串的个数
* 基本思路是计算A的所有后缀和B的所有后缀之间的最长公共前缀的长度,
* 把最长公共前缀长度不小于k的部分全部加起来。
* 先把两个字符串连起来,中间用一个没有用过的字符隔开。
* 按height分组后,接下来便是快速的统计每组中后缀之间的最长公共前缀之和
* 用一个单调的栈来维护,每遇到一个B的后缀就统计与前面的A的后缀
* 能产生多少个长度不小于k的公共子串。最A也一样做一边
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cstdlib>
const int maxn = 200000 + 10;
using namespace std;
int sa[maxn];
int rank[maxn], height[maxn];
int t1[maxn],t2[maxn],c[maxn];
//求sa数组需要的中间变量,不需要赋值
//带排序的字符串放在s数组中,从s[0] - s[n-1],
//长度为n,且最大值小于m
//除了s[n-1]之外的所有s[i] 都大于0,r[n-1] = 0
//函数结束后,结果放在sa数组中
bool cmp(int *r,int a,int b,int l){
return r[a] == r[b] && r[a+l] == r[b+l];
}
void da(int str[],int sa[],int rank[],int height[],int n,int m){
n++;
int i,j,p,*x = t1,*y = t2;
for(i = 0;i< m ;i ++) c[i] = 0;
for(i=0;i<n;i++) c[x[i] = str[i] ]++;
for(i=1;i<m;i++) c[i] += c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[i] ]] = i;
for(j=1;j<=n;j<<=1){
p = 0;
//直接利用sa数组排序第二关键字
for(i=n-j;i<n;i++) y[p++] = i;
for(i=0;i<n;i++) if(sa[i] >= j) y[p++] = sa[i] - j;
//这样数组y保存的就是按照第二关键字排序的结果
//基数排序第一关键字
for(i=0;i<m;i++) c[i] = 0;
for(i=0;i<n;i++) c[x[y[i]] ]++;
for(i=1;i<m;i++) c[i] += c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[y[i] ]] ] = y[i];
//根据sa和x数组计算新的x数组
swap(x,y);
p = 1;x[ sa[0]] = 0;
for(i=1;i<n;i++)
x[sa[i] ] = cmp(y,sa[i-1],sa[i],j) ? p-1:p++;
if(p >= n) break;
m = p; //下次基数排序的最大值
}
int k = 0;
n--;
for(i=0;i<=n;i++) rank[sa[i] ] = i;
for(i=0;i<n;i++){
if(k) k--;
j = sa[rank[i] - 1 ];
while(str[i+k] == str[j+k] ) k++;
height[rank[i] ] = k;
}
}
char str1[maxn], str2[maxn];
int r[maxn];
int sta[maxn],stb[maxn];
typedef long long ll;
int main(){
int k,n,len1,len2;
while(scanf("%d",&k) == 1 && k){
scanf("%s%s",str1,str2);
len1 =strlen(str1);
len2 =strlen(str2);
n = len1 + len2 + 1;
for(int i=0;i<len1;i++) r[i] = str1[i];
r[len1]= 1; // split character
for(int i=0;i<len2;i++) r[i+len1+1] = str2[i];
r[len1 + len2 + 1] = 0;
da(r,sa,rank,height,n,128);
ll ans = 0 ,ss = 0;
int top = 0;
for(int i=2;i<=n;i++){
if(height[i] < k){
ss = 0;
top = 0;
continue;
}
int cnt = 0;
if(sa[i-1] < len1){
cnt++;
ss += height[i] - k + 1;
}
while(top > 0 && height[i] <= sta[top-1]){
top--;
ss -= stb[top] * (sta[top] - height[i]);
cnt += stb[top];
}
sta[top] = height[i];
stb[top++] = cnt;
if(sa[i] > len1) ans += ss;
}
ss = 0; top = 0;
for(int i=2;i<=n;i++){
if(height[i] < k){
ss = 0;
top = 0;
continue;
}
int cnt = 0;
if(sa[i - 1] > len1){
cnt++;
ss += height[i] - k + 1;
}
while(top > 0 && height[i] <= sta[top - 1]){
top --;
ss -= stb[top] * (sta[top] - height[i]);
cnt += stb[top];
}
sta[top] = height[i];
stb[top++] = cnt;
if(sa[i] < len1) ans += ss;
}
printf("%lld\n",ans);
}
return 0;
}
/* * poj 3294 * 给出n个字符串,求出现在一半以上字符串的最长子串,按照字典序输出所有结果 * 将n个字符串连接起来,中间用没有出现过的字符隔开,然后求后缀数组。 然后二分答案,进行分组,判断每组的后缀是否出现在不少于k个的原串中, */ #include <iostream> #include <cstdio> #include <cstring> #include <string> #include <algorithm> #include <cstdlib> const int maxn = 200000 + 10; using namespace std; int sa[maxn]; int rank[maxn], height[maxn]; int t1[maxn],t2[maxn],c[maxn]; //求sa数组需要的中间变量,不需要赋值 //带排序的字符串放在s数组中,从s[0] - s[n-1], //长度为n,且最大值小于m //除了s[n-1]之外的所有s[i] 都大于0,r[n-1] = 0 //函数结束后,结果放在sa数组中 bool cmp(int *r,int a,int b,int l){ return r[a] == r[b] && r[a+l] == r[b+l]; } void da(int str[],int sa[],int rank[],int height[],int n,int m){ n++; int i,j,p,*x = t1,*y = t2; for(i = 0;i< m ;i ++) c[i] = 0; for(i=0;i<n;i++) c[x[i] = str[i] ]++; for(i=1;i<m;i++) c[i] += c[i-1]; for(i=n-1;i>=0;i--) sa[--c[x[i] ]] = i; for(j=1;j<=n;j<<=1){ p = 0; //直接利用sa数组排序第二关键字 for(i=n-j;i<n;i++) y[p++] = i; for(i=0;i<n;i++) if(sa[i] >= j) y[p++] = sa[i] - j; //这样数组y保存的就是按照第二关键字排序的结果 //基数排序第一关键字 for(i=0;i<m;i++) c[i] = 0; for(i=0;i<n;i++) c[x[y[i]] ]++; for(i=1;i<m;i++) c[i] += c[i-1]; for(i=n-1;i>=0;i--) sa[--c[x[y[i] ]] ] = y[i]; //根据sa和x数组计算新的x数组 swap(x,y); p = 1;x[ sa[0]] = 0; for(i=1;i<n;i++) x[sa[i] ] = cmp(y,sa[i-1],sa[i],j) ? p-1:p++; if(p >= n) break; m = p; //下次基数排序的最大值 } int k = 0; n--; for(i=0;i<=n;i++) rank[sa[i] ] = i; for(i=0;i<n;i++){ if(k) k--; j = sa[rank[i] - 1 ]; while(str[i+k] == str[j+k] ) k++; height[rank[i] ] = k; } } int RMQ[maxn]; int mm[maxn]; int best[20][maxn]; void initRMQ(int n){ mm[0] = -1; for(int i=1;i<=n;i++) mm[i] = ((i & (i-1))==0 ) ? mm[i-1] +1 : mm[i-1]; for(int i=1;i<=n;i++) best[0][i] = i; for(int i=1;i<=mm[n];i++) for(int j=1;j+(1<<i)-1 <=n;j++){ int a = best[i-1][j]; int b = best[i-1][j+(1<<(i-1)) ]; if(height[a] < height[b]) best[i][j] = a; else best[i][j] = b; } } int askRMQ(int a,int b){ int t; t = mm[b-a+1]; b-=(1<<t)-1; a = best[t][a] ; b = best[t][b]; return height[a] < height[b] ? a:b; } int lcp(int a,int b){ a = rank[a]; b = rank[b]; if(a > b) swap(a,b); return height[askRMQ(a+1,b)]; } int n; char str[110][1010]; int st[110], ed[110] ; // 各个字符串对应的起始和结束 bool used[110]; // 标记 int who[maxn]; int r[maxn]; int check(int totlen,int len, int k){ memset(used,0,sizeof used); int ret = 0; int tmp = who[ sa[1]]; if(tmp != -1 && used[tmp] == false){ ret++; used[tmp] = true; } if(ret >= k) return 1; for(int i=2;i<=totlen;i++){ if(height[i] < len){ ret = 0; memset(used,false,sizeof used); tmp = who[sa[i] ]; if(tmp != -1 && used[ tmp] == false){ ret ++; used[tmp] = true; } if(ret >=k) return i; }else{ tmp = who[sa[i] ]; if(tmp != -1 && used[tmp] == false){ ret++; used[tmp] = true; } if(ret >= k) return i; } } return -1; } void output(int totlen, int len, int k){ memset(used,0,sizeof used); int ret = 0; int tmp = who[sa[1] ]; if(tmp != 1 && used[tmp] == false){ ret++; used[tmp] = true; } for(int i=2;i<=totlen;i++){ if(height[i] < len){ if(ret >= k){ for(int j=0;j<len;j++) printf("%c",r[sa[i-1] + j]); printf("\n"); } ret = 0; memset(used,0,sizeof used); tmp = who[sa[i]]; if(tmp != -1 && used[tmp] == false){ ret++; used[tmp] = true; } }else{ tmp = who[sa[i]]; if(tmp != -1 && used[tmp] == false){ ret++; used[tmp] = true; } } } if(ret >= k){ for(int j=0;j<len;j++) printf("%c",r[sa[totlen] + j]); printf("\n"); } } int main(){ int totlen; bool first = true; while(scanf("%d",&n) == 1 && n){ if(first) first =false; else printf("\n"); totlen = 0; for(int i=0;i<n;i++){ scanf("%s",str[i]); int len = strlen(str[i]); for(int j=0;j<len;j++){ r[totlen+ j] = str[i][j]; who[totlen + j] = i; } r[totlen + len] = i + 130; who[totlen + len ] = -1; totlen += len + 1; } totlen --; r[totlen] = 0; da(r,sa,rank,height,totlen,300); int k = n/2 + 1; int ans = -1; int left = 1, right = 1010; while(left <= right){int mid = (left + right) >> 1; int x = check(totlen,mid,k); if(x == -1){ right = mid - 1; }else{ ans = mid; left = mid + 1; } } if(ans <= 0) printf("?\n"); else{ output(totlen,ans,k); } } return 0;} spoj220/* 给定 n 个字符串,求在每个字符串中至少出现两次且不重叠的最长子串。 算法分析: 做法和上题大同小异,也是先将 n 个字符串连起来,中间用不相同的且没有 出现在字符串中的字符隔开,求后缀数组。然后二分答案,再将后缀分组。判断 的时候,要看是否有一组后缀在每个原来的字符串中至少出现两次,并且在每个 原来的字符串中,后缀的起始位置的最大值与最小值之差是否不小于当前答案 (判断能否做到不重叠,如果题目中没有不重叠的要求,那么不用做此判断)。 这个做法的时间复杂度为 O(nlogn)。 */ #include <iostream> #include <cstdio> #include <cstring> #include <string> #include <algorithm> #include <cstdlib> const int maxn = 200000 + 10; using namespace std; const int INF = 0x3f3f3f3f; int sa[maxn]; int rank[maxn], height[maxn]; int t1[maxn],t2[maxn],c[maxn]; //求sa数组需要的中间变量,不需要赋值 //带排序的字符串放在s数组中,从s[0] - s[n-1], //长度为n,且最大值小于m //除了s[n-1]之外的所有s[i] 都大于0,r[n-1] = 0 //函数结束后,结果放在sa数组中 bool cmp(int *r,int a,int b,int l){ return r[a] == r[b] && r[a+l] == r[b+l]; } void da(int str[],int sa[],int rank[],int height[],int n,int m){ n++; int i,j,p,*x = t1,*y = t2; for(i = 0;i< m ;i ++) c[i] = 0; for(i=0;i<n;i++) c[x[i] = str[i] ]++; for(i=1;i<m;i++) c[i] += c[i-1]; for(i=n-1;i>=0;i--) sa[--c[x[i] ]] = i; for(j=1;j<=n;j<<=1){ p = 0; //直接利用sa数组排序第二关键字 for(i=n-j;i<n;i++) y[p++] = i; for(i=0;i<n;i++) if(sa[i] >= j) y[p++] = sa[i] - j; //这样数组y保存的就是按照第二关键字排序的结果 //基数排序第一关键字 for(i=0;i<m;i++) c[i] = 0; for(i=0;i<n;i++) c[x[y[i]] ]++; for(i=1;i<m;i++) c[i] += c[i-1]; for(i=n-1;i>=0;i--) sa[--c[x[y[i] ]] ] = y[i]; //根据sa和x数组计算新的x数组 swap(x,y); p = 1;x[ sa[0]] = 0; for(i=1;i<n;i++) x[sa[i] ] = cmp(y,sa[i-1],sa[i],j) ? p-1:p++; if(p >= n) break; m = p; //下次基数排序的最大值 } int k = 0; n--; for(i=0;i<=n;i++) rank[sa[i] ] = i; for(i=0;i<n;i++){ if(k) k--; j = sa[rank[i] - 1 ]; while(str[i+k] == str[j+k] ) k++; height[rank[i] ] = k; } } int RMQ[maxn]; int mm[maxn]; int best[20][maxn]; void initRMQ(int n){ mm[0] = -1; for(int i=1;i<=n;i++) mm[i] = ((i & (i-1))==0 ) ? mm[i-1] +1 : mm[i-1]; for(int i=1;i<=n;i++) best[0][i] = i; for(int i=1;i<=mm[n];i++) for(int j=1;j+(1<<i)-1 <=n;j++){ int a = best[i-1][j]; int b = best[i-1][j+(1<<(i-1)) ]; if(RMQ[a] < RMQ[b]) best[i][j] = a; else best[i][j] = b; } } int askRMQ(int a,int b){ int t; t = mm[b-a+1]; b-=(1<<t)-1; a = best[t][a] ; b = best[t][b]; return RMQ[a] < RMQ[b] ? a:b; } int lcp(int a,int b){ a = rank[a]; b = rank[b]; if(a > b) swap(a,b); return height[askRMQ(a+1,b)]; } char ch[maxn]; int str[maxn]; int l[105]; int mx[15], mn[15]; int in[maxn]; int k; bool check(int mid,int n){ for(int i=0;i<k;i++) { mx[i] = 0; mn[i] = INF; } for(int i=1;i<=n;i++){ if(height[i] < mid){ for(int j=0;j<k;j++){ mx[j] = 0; mn[j] = INF; } mx[in[sa[i]]] = sa[i]; mn[in[sa[i]]] = sa[i]; }else{ mx[in[sa[i]]] = max(mx[in[sa[i]]], sa[i]); mn[in[sa[i]]] = min(mn[in[sa[i]]], sa[i]); mx[in[sa[i-1]]] = max(mx[in[sa[i-1]]],sa[i-1]); mn[in[sa[i-1]]] = min(mn[in[sa[i-1]]],sa[i-1]); int j; for(j=0;j<k;j++){ if(mx[j] - mn[j] < mid) break; } if(j == k) return true; } } return false; } int main(){ int cnt = 0,t; scanf("%d",&t); while(t--){ scanf("%d",&k); int n = 0; for(int i=0;i<k;i++){ scanf("%s",ch); l[i] = strlen(ch); for(int j=n;j<n+ l[i]; j++){ str[j] = ch[j - n] - 'a' + 1; in[j] = i; } n += l[i] + 1; str[n - 1] = 27 + i; } n--; str[n] = 0; da(str,sa,rank,height,n,27 + k + 5); int low = 0 , high = 10000, mid,ans = 0; while(low <= high){ mid = (low + high) /2; if(check(mid,n)){ ans = mid; low = mid + 1; }else high = mid - 1; } printf("%d\n",ans); } return 0; }
HDU4552
/* HDU 4552 怪盗基德的挑战书(后缀数组) 题目就是求前缀出现的次数。 用后缀数组求的话,就是求出每个后缀和最长的后缀的公共前缀长度就可以了。 就是rank[0]的位置往两边找。 这题数据很水,暴力都可过。 用KMP做也很简单 */ #include <iostream> #include <cstdio> #include <cstring> #include <string> #include <algorithm> #include <cstdlib> const int maxn = 200000 + 10; using namespace std; const int INF = 0x3f3f3f3f; int sa[maxn]; int Rank[maxn], height[maxn]; int t1[maxn],t2[maxn],c[maxn]; //求sa数组需要的中间变量,不需要赋值 //带排序的字符串放在s数组中,从s[0] - s[n-1], //长度为n,且最大值小于m //除了s[n-1]之外的所有s[i] 都大于0,r[n-1] = 0 //函数结束后,结果放在sa数组中 bool cmp(int *r,int a,int b,int l){ return r[a] == r[b] && r[a+l] == r[b+l]; } void da(int str[],int sa[],int Rank[],int height[],int n,int m){ n++; int i,j,p,*x = t1,*y = t2; for(i = 0;i< m ;i ++) c[i] = 0; for(i=0;i<n;i++) c[x[i] = str[i] ]++; for(i=1;i<m;i++) c[i] += c[i-1]; for(i=n-1;i>=0;i--) sa[--c[x[i] ]] = i; for(j=1;j<=n;j<<=1){ p = 0; //直接利用sa数组排序第二关键字 for(i=n-j;i<n;i++) y[p++] = i; for(i=0;i<n;i++) if(sa[i] >= j) y[p++] = sa[i] - j; //这样数组y保存的就是按照第二关键字排序的结果 //基数排序第一关键字 for(i=0;i<m;i++) c[i] = 0; for(i=0;i<n;i++) c[x[y[i]] ]++; for(i=1;i<m;i++) c[i] += c[i-1]; for(i=n-1;i>=0;i--) sa[--c[x[y[i] ]] ] = y[i]; //根据sa和x数组计算新的x数组 swap(x,y); p = 1;x[ sa[0]] = 0; for(i=1;i<n;i++) x[sa[i] ] = cmp(y,sa[i-1],sa[i],j) ? p-1:p++; if(p >= n) break; m = p; //下次基数排序的最大值 } int k = 0; n--; for(i=0;i<=n;i++) Rank[sa[i] ] = i; for(i=0;i<n;i++){ if(k) k--; j = sa[Rank[i] - 1 ]; while(str[i+k] == str[j+k] ) k++; height[Rank[i] ] = k; } } int RMQ[maxn]; int mm[maxn]; int best[20][maxn]; void initRMQ(int n){ mm[0] = -1; for(int i=1;i<=n;i++) mm[i] = ((i & (i-1))==0 ) ? mm[i-1] +1 : mm[i-1]; for(int i=1;i<=n;i++) best[0][i] = i; for(int i=1;i<=mm[n];i++) for(int j=1;j+(1<<i)-1 <=n;j++){ int a = best[i-1][j]; int b = best[i-1][j+(1<<(i-1)) ]; if(RMQ[a] < RMQ[b]) best[i][j] = a; else best[i][j] = b; } } int askRMQ(int a,int b){ int t; t = mm[b-a+1]; b-=(1<<t)-1; a = best[t][a] ; b = best[t][b]; return RMQ[a] < RMQ[b] ? a:b; } int lcp(int a,int b){ a = Rank[a]; b = Rank[b]; if(a > b) swap(a,b); return height[askRMQ(a+1,b)]; } char str[maxn]; int s[maxn]; /* suffix(j)和suffix(k)的最长公共前缀为height[rank[j]+1],height[rank[j]+2],height[rank[j]+3],……,height[rank[k]]中的最小值。 由此从第一个字符的后缀开始,依照后缀排名的顺序左右扫描 不太明白。 */ int main(){ while(scanf("%s",str) == 1){ int n = strlen(str); for(int i=0;i<=n;i++) s[i] =str[i]; da(s,sa,Rank,height,n,128); int ans = n; int t = Rank[0]; int tmp = n; while(t < n){ tmp = min(tmp, height[ t + 1]); t++; ans += tmp; } t =Rank[0]; tmp = n; while(t > 1){ tmp = min(tmp,height[t]); t--; ans += tmp; } printf("%d\n",ans % 256); } return 0; }
HDU4691 求最长公共前缀
/* HDU4691 求最长公共前缀 */ #include <iostream> #include <cstdio> #include <cstring> #include <string> #include <algorithm> #include <cstdlib> const int maxn = 100000 + 10; using namespace std; const int INF = 0x3f3f3f3f; int sa[maxn]; int Rank[maxn], height[maxn]; int t1[maxn],t2[maxn],c[maxn]; //求sa数组需要的中间变量,不需要赋值 //带排序的字符串放在s数组中,从s[0] - s[n-1], //长度为n,且最大值小于m //除了s[n-1]之外的所有s[i] 都大于0,r[n-1] = 0 //函数结束后,结果放在sa数组中 bool cmp(int *r,int a,int b,int l){ return r[a] == r[b] && r[a+l] == r[b+l]; } void da(int str[],int sa[],int Rank[],int height[],int n,int m){ n++; int i,j,p,*x = t1,*y = t2; for(i = 0;i< m ;i ++) c[i] = 0; for(i=0;i<n;i++) c[x[i] = str[i] ]++; for(i=1;i<m;i++) c[i] += c[i-1]; for(i=n-1;i>=0;i--) sa[--c[x[i] ]] = i; for(j=1;j<=n;j<<=1){ p = 0; //直接利用sa数组排序第二关键字 for(i=n-j;i<n;i++) y[p++] = i; for(i=0;i<n;i++) if(sa[i] >= j) y[p++] = sa[i] - j; //这样数组y保存的就是按照第二关键字排序的结果 //基数排序第一关键字 for(i=0;i<m;i++) c[i] = 0; for(i=0;i<n;i++) c[x[y[i]] ]++; for(i=1;i<m;i++) c[i] += c[i-1]; for(i=n-1;i>=0;i--) sa[--c[x[y[i] ]] ] = y[i]; //根据sa和x数组计算新的x数组 swap(x,y); p = 1;x[ sa[0]] = 0; for(i=1;i<n;i++) x[sa[i] ] = cmp(y,sa[i-1],sa[i],j) ? p-1:p++; if(p >= n) break; m = p; //下次基数排序的最大值 } int k = 0; n--; for(i=0;i<=n;i++) Rank[sa[i] ] = i; for(i=0;i<n;i++){ if(k) k--; j = sa[Rank[i] - 1 ]; while(str[i+k] == str[j+k] ) k++; height[Rank[i] ] = k; } } int RMQ[maxn]; int mm[maxn]; int best[20][maxn]; void initRMQ(int n){ mm[0] = -1; for(int i=1;i<=n;i++) mm[i] = ((i & (i-1))==0 ) ? mm[i-1] +1 : mm[i-1]; for(int i=1;i<=n;i++) best[0][i] = i; for(int i=1;i<=mm[n];i++) for(int j=1;j+(1<<i)-1 <=n;j++){ int a = best[i-1][j]; int b = best[i-1][j+(1<<(i-1)) ]; if(RMQ[a] < RMQ[b]) best[i][j] = a; else best[i][j] = b; } } int askRMQ(int a,int b){ int t; t = mm[b-a+1]; b-=(1<<t)-1; a = best[t][a] ; b = best[t][b]; return RMQ[a] < RMQ[b] ? a:b; } int lcp(int a,int b){ a = Rank[a]; b = Rank[b]; if(a > b) swap(a,b); return height[askRMQ(a+1,b)]; } char str[maxn]; int r[maxn]; int A[maxn], B[maxn]; int calc(int n){ if(n == 0) return 1; int ret = 0; while(n){ ret++; n /= 10; } return ret; } typedef long long ll; int main(){ while(scanf("%s",str) == 1){ int n = strlen(str); for(int i=0;i<n;i++) r[i] = str[i]; r[n] = 0; da(r,sa,Rank,height,n,128); for(int i=1;i<=n;i++) RMQ[i] = height[i]; initRMQ(n); int k,u,v; ll ans1 =0 , ans2 = 0; scanf("%d",&k); for(int i=0;i<k;i++){ scanf("%d%d",&A[i], &B[i]); if(i == 0){ ans1 += B[i] - A[i] +1; // 末尾 ans2 += B[i] - A[i] +3; // 加个0 空格 末尾多一个空格 continue; } int tmp; if(A[i] != A[i-1]) tmp = lcp(A[i] , A[i-1]); else tmp = 10000000; tmp = min(tmp, B[i] -A[i]); tmp = min(tmp, B[i -1 ] -A[i -1]); ans1 += B[i] - A[i] +1; // tmp 为有多少重复的 // cout<<"----------"<<endl; // cout<<ans2<<endl; ans2 += B[i] - A[i] - tmp +1; // cout<<ans2<<endl; ans2 += 1; // 空格 // cout<<ans2<<endl; //cout<<tmp<<endl; ans2 += calc(tmp); // 计算匹配长度这个数字的长度 // cout<<ans2<<endl; //cout<<"----------"<<endl; } printf("%lld %lld\n",ans1,ans2); } return 0; }
总结
height 数组:定义 height[i]=suffix(sa[i-1])和 suffix(sa[i])的最长公
共前缀,也就是排名相邻的两个后缀的最长公共前缀。那么对于 j 和 k,不妨设
rank[j]<rank[k],则有以下性质:
suffix(j) 和 suffix(k) 的 最 长 公 共 前 缀 为 height[rank[j]+1],
height[rank[j]+2], height[rank[j]+3], ... ,height[rank[k]]中的最小值。
后缀数组:后缀数组 SA 是一个一维数组,它保存 1..n 的某个排列 SA[1],
SA[2],......,SA[n],并且保证 Suffix(SA[i]) < Suffix(SA[i+1]),1≤i<n。
也就是将 S 的 n 个后缀从小到大进行排序之后把排好序的后缀的开头位置顺
次放入 SA 中。
名次数组:名次数组 Rank[i]保存的是 Suffix(i)在所有后缀中从小到大排
列的“名次”。
简单的说,后缀数组是“排第几的是谁?”,名次数组是“你排第几?”。容
易看出,后缀数组和名次数组为互逆运算。
本文深入讲解后缀数组及其高度数组的应用,包括最长公共子串、重复子串等问题的解决方法,并提供多个实例代码。

571

被折叠的 条评论
为什么被折叠?



