水题
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)在所有后缀中从小到大排
列的“名次”。
简单的说,后缀数组是“排第几的是谁?”,名次数组是“你排第几?”。容
易看出,后缀数组和名次数组为互逆运算。