后缀数组总结

水题

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));
        }
 
}

URAL 1297 Palindrome(后缀数组求最长回文子串) 后缀数组加RMQ

给定一个字符串,求最长回文子串。
算法分析:
穷举每一位,然后计算以这个字符为中心的最长回文子串。注意这里要分两
种情况,一是回文子串的长度为奇数,二是长度为偶数。两种情况都可以转化为
求一个后缀和一个反过来写的后缀的最长公共前缀。具体的做法是:将整个字符
串反过来写在原字符串后面,中间用一个特殊的字符隔开。这样就把问题变为了
求这个新的字符串的某两个后缀的最长公共前缀。(论文)

#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;
}


2.3两个字符串的相关问题
这类问题的一个常用做法是,先连接这两个字符串,然后求后缀数组和
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;
}



POJ3294


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


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值