后缀数组总结

水题

POJ2774

求两个串的最长公共子串(最长公共前缀)。


求两个数的最长公共子串,可以将两个字符串中间加一个‘$’然后拼接成一个子串。

这样两个最长公共子串问题转换为 一个大串的最长公共前缀,因为height数组存的是 i-1 和 i位置的最长公共前缀,因此只要保证 i-1 和 i 位置的子串是不同的字符串即可。


#include 
#include 
#include 
#include 
#include 
#include 
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=0;i--) sa[--c[x[i] ]] = i;
    for(j=1;j<=n;j<<=1){
        p = 0;
        //直接利用sa数组排序第二关键字
        for(i=n-j;i= j) y[p++] = sa[i] - j;
        //这样数组y保存的就是按照第二关键字排序的结果
        //基数排序第一关键字
        for(i=0;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) break;
        m = p; //下次基数排序的最大值
    }
    int k = 0;
    n--;
    for(i=0;i<=n;i++) rank[sa[i] ] = i;
    for(i=0;i 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=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 
#include 
#include 
#include 
#include 
#include 
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=0;i--) sa[--c[x[i] ]] = i;
    for(j=1;j<=n;j<<=1){
        p = 0;
        //直接利用sa数组排序第二关键字
        for(i=n-j;i= j) y[p++] = sa[i] - j;
        //这样数组y保存的就是按照第二关键字排序的结果
        //基数排序第一关键字
        for(i=0;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) break;
        m = p; //下次基数排序的最大值
    }
    int k = 0;
    n--;
    for(i=0;i<=n;i++) rank[sa[i] ] = i;
    for(i=0;iMax)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;i0;i--) s[i] = s[i] - s[i-1] + 90;
        n--; //减少一个长度
        for(int i=0;i


POJ3261

可重叠的K次(大于等于K)最长重复子串

#include 
#include 
#include 
#include 
#include 
#include 
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=0;i--) sa[--c[x[i] ]] = i;
    for(j=1;j<=n;j<<=1){
        p = 0;
        //直接利用sa数组排序第二关键字
        for(i=n-j;i= j) y[p++] = sa[i] - j;
        //这样数组y保存的就是按照第二关键字排序的结果
        //基数排序第一关键字
        for(i=0;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) break;
        m = p; //下次基数排序的最大值
    }
    int k = 0;
    n--;
    for(i=0;i<=n;i++) rank[sa[i] ] = i;
    for(i=0;i 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>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 
#include 
#include 
#include 
#include 
#include 
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=0;i--) sa[--c[x[i] ]] = i;
    for(j=1;j<=n;j<<=1){
        p = 0;
        //直接利用sa数组排序第二关键字
        for(i=n-j;i= j) y[p++] = sa[i] - j;
        //这样数组y保存的就是按照第二关键字排序的结果
        //基数排序第一关键字
        for(i=0;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) break;
        m = p; //下次基数排序的最大值
    }
    int k = 0;
    n--;
    for(i=0;i<=n;i++) rank[sa[i] ] = i;
    for(i=0;i

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

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

#include 
#include 
#include 
#include 
#include 
#include 
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=0;i--) sa[--c[x[i] ]] = i;
    for(j=1;j<=n;j<<=1){
        p = 0;
        //直接利用sa数组排序第二关键字
        for(i=n-j;i= j) y[p++] = sa[i] - j;
        //这样数组y保存的就是按照第二关键字排序的结果
        //基数排序第一关键字
        for(i=0;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) break;
        m = p; //下次基数排序的最大值
    }
    int k = 0;
    n--;
    for(i=0;i<=n;i++) rank[sa[i] ] = i;
    for(i=0;i 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 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 
#include 
#include 
#include 
#include 
#include 
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=0;i--) sa[--c[x[i] ]] = i;
    for(j=1;j<=n;j<<=1){
        p = 0;
        //直接利用sa数组排序第二关键字
        for(i=n-j;i= j) y[p++] = sa[i] - j;
        //这样数组y保存的就是按照第二关键字排序的结果
        //基数排序第一关键字
        for(i=0;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) break;
        m = p; //下次基数排序的最大值
    }
    int k = 0;
    n--;
    for(i=0;i<=n;i++) rank[sa[i] ] = i;
    for(i=0;i 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= 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= (maxx-1)*l ){
                    len = l;
                    st = sa[i];
                    break;
                }
            }
        }
       // cout<


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 
#include 
#include 
#include 
#include 
#include 
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=0;i--) sa[--c[x[i] ]] = i;
    for(j=1;j<=n;j<<=1){
        p = 0;
        //直接利用sa数组排序第二关键字
        for(i=n-j;i= j) y[p++] = sa[i] - j;
        //这样数组y保存的就是按照第二关键字排序的结果
        //基数排序第一关键字
        for(i=0;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) break;
        m = p; //下次基数排序的最大值
    }
    int k = 0;
    n--;
    for(i=0;i<=n;i++) rank[sa[i] ] = i;
    for(i=0;i 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 
#include 
#include 
#include 
#include 
#include 
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=0;i--) sa[--c[x[i] ]] = i;
    for(j=1;j<=n;j<<=1){
        p = 0;
        //直接利用sa数组排序第二关键字
        for(i=n-j;i= j) y[p++] = sa[i] - j;
        //这样数组y保存的就是按照第二关键字排序的结果
        //基数排序第一关键字
        for(i=0;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) break;
        m = p; //下次基数排序的最大值
    }
    int k = 0;
    n--;
    for(i=0;i<=n;i++) rank[sa[i] ] = i;
    for(i=0;i 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= k){
        for(int j=0;j

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 
#include 
#include 
#include 
#include 
#include 
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=0;i--) sa[--c[x[i] ]] = i;
    for(j=1;j<=n;j<<=1){
        p = 0;
        //直接利用sa数组排序第二关键字
        for(i=n-j;i= j) y[p++] = sa[i] - j;
        //这样数组y保存的就是按照第二关键字排序的结果
        //基数排序第一关键字
        for(i=0;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) break;
        m = p; //下次基数排序的最大值
    }
    int k = 0;
    n--;
    for(i=0;i<=n;i++) rank[sa[i] ] = i;
    for(i=0;i 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


HDU4552 

/*
HDU 4552 怪盗基德的挑战书(后缀数组)
题目就是求前缀出现的次数。

用后缀数组求的话,就是求出每个后缀和最长的后缀的公共前缀长度就可以了。

就是rank[0]的位置往两边找。


这题数据很水,暴力都可过。

用KMP做也很简单
 */
#include 
#include 
#include 
#include 
#include 
#include 
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=0;i--) sa[--c[x[i] ]] = i;
    for(j=1;j<=n;j<<=1){
        p = 0;
        //直接利用sa数组排序第二关键字
        for(i=n-j;i= j) y[p++] = sa[i] - j;
        //这样数组y保存的就是按照第二关键字排序的结果
        //基数排序第一关键字
        for(i=0;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) break;
        m = p; //下次基数排序的最大值
    }
    int k = 0;
    n--;
    for(i=0;i<=n;i++) Rank[sa[i] ] = i;
    for(i=0;i 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 
#include 
#include 
#include 
#include 
#include 
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=0;i--) sa[--c[x[i] ]] = i;
    for(j=1;j<=n;j<<=1){
        p = 0;
        //直接利用sa数组排序第二关键字
        for(i=n-j;i= j) y[p++] = sa[i] - j;
        //这样数组y保存的就是按照第二关键字排序的结果
        //基数排序第一关键字
        for(i=0;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) break;
        m = p; //下次基数排序的最大值
    }
    int k = 0;
    n--;
    for(i=0;i<=n;i++) Rank[sa[i] ] = i;
    for(i=0;i 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



总结

height 数组:定义 height[i]=suffix(sa[i-1])和 suffix(sa[i])的最长公
共前缀,也就是排名相邻的两个后缀的最长公共前缀。那么对于 j 和 k,不妨设
rank[j] 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 也就是将 S 的 n 个后缀从小到大进行排序之后把排好序的后缀的开头位置顺
次放入 SA 中。
名次数组:名次数组 Rank[i]保存的是 Suffix(i)在所有后缀中从小到大排
列的“名次”。
简单的说,后缀数组是“排第几的是谁?”,名次数组是“你排第几?”。容
易看出,后缀数组和名次数组为互逆运算。


你可能感兴趣的:(后缀数组总结)