后缀数组模板题总结

Posted philo-zhou

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了后缀数组模板题总结相关的知识,希望对你有一定的参考价值。

出大问题,我原来学了几天,然后刷了七八题吧,之后将近半年一次没用到, 今天回头一想后缀数组??????????

所以来总结一下,省的模板题都不会了。

rk[i] 第i个后缀的排名; SA[i] 排名为i的后缀位置; Height[i] 排名为i的后缀与排名为(i-1)的后缀的LCP

POJ - 3261:找出出现k次的可重叠的最长子串的长度。 https://vjudge.net/contest/283743#problem/D

思路:直接二分次数就行了,在判断里面看是连续>=k的最多有几个。

技术图片
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#define ll long long
#define rep(i, a, b) for(int i = a; i <= b; i++)
#define pb push_back
#define mp make_pair
using namespace std;

const int mod = 1e9 + 7;
const ll INF = 1e15;
const double eps = 1e-8;
const int maxn = 2e5 + 10;

int sa[maxn],rk[maxn], height[maxn], h[maxn], y[maxn], x[maxn], c[maxn];
int n, m;
int s[maxn];
void get_sa(){
    int tt;
    rep(i, 1, n) c[x[i] = s[i]]++;
    rep(i, 2, m) c[i] += c[i - 1];
    for(int i = n; i >= 1; i--) sa[c[x[i]]--] = i;
    
    //基数排序
    for(int k = 1; k <= n; k<<= 1){
        int num = 0;
        for(int i = n - k + 1; i <= n; i++) y[++num] = i;
        rep(i, 1, n) if(sa[i] > k) y[++num] = sa[i] - k;
        rep(i, 0, m) c[i] = 0;
        rep(i, 1, n) c[x[i]]++;
        rep(i, 2, m) c[i] += c[i - 1];
        for(int i = n; i >= 1; i--) sa[c[x[y[i]]]--] = y[i], y[i] = 0;
        rep(i, 0, n) {
            tt = x[i];
            x[i] = y[i];
            y[i] = tt;
        }
        x[sa[1]] = 1, num = 1;
        rep(i, 2, n) x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k]) ? num : ++num;
        if(num == n) break;
        m = num;
    }
}
void get_height(){
    int k = 0;
    rep(i, 1, n) rk[sa[i]] = i;
    rep(i, 1, n){
        if(rk[i] == 1) continue;
        if(k) k--;//h[i] >= h[i - 1] - 1
        int j = sa[rk[i] - 1];
        while(j + k <= n && i + k <= n && s[j + k] == s[i + k]) k++;
        height[rk[i]] = k;
    }
}

bool judge(int len, int k){
    int have = 1;
    rep(i, 2, n){
        if(height[i] >= len) have++;
        else have = 1;
        if(have >= k) return 1;
    }
    return 0;
}


int main(){
    int k; scanf("%d%d",&n, &k);
    m = 1e5;
    rep(i, 1, n){
        scanf("%d",&s[i]);
        m = max(m, ++s[i]);
    }
    get_sa();
    get_height();
    int l = 1, r = n;
    int ans = 1;
    while(l <= r){
        int mid = (l+r) / 2;
        if(judge(mid, k)){
            ans = mid;
            l = mid+1;
        }
        else r = mid-1;
    }
    cout << ans << endl;
    return 0;
}
View Code

2  SPOJ - DISUBSTR:不同子串的个数。 https://vjudge.net/contest/283743#problem/E

思路:所有的子串和是(n+1)*n/2,我们现在还需要个重复的个数,这样总的减去重复的就是我们要的。考虑height数组含义,和上一后缀的最长公共前缀的长度。

举个例子:

后缀sa[i-1]: aaabbd

后缀 sa[i] : aabbd

重复的是aa,现在我们要减去aa有多少个不同的子串,然后你发现这个重复的一定是同一个字母,不可能出现aaaab这种情况,因为这是后缀,你可以随便试试。这种串的子串个数就等于串的长度,也就是height。

那么答案出来了ans = (n+1)*n/2 - height[2 ~ n]; 因为height[1]是排名1的与上一位的最长公共前缀,所以它前边都没有。

技术图片
#include <bits/stdc++.h>
#define ll long long
#define rep(i, a, b) for(int i = a; i <= b; i++)
#define pb push_back
#define mp make_pair
using namespace std;

const int mod = 1e9 + 7;
const ll INF = 1e15;
const double eps = 1e-8;
const int maxn = 2e3 + 10;

int sa[maxn],rk[maxn], height[maxn], h[maxn], y[maxn], x[maxn], c[maxn];
int n, m;
char s[maxn];
void get_sa(){
    int tt;
    rep(i, 0, m) c[i] = 0;
    rep(i, 1, n) c[x[i] = s[i]]++;
    rep(i, 2, m) c[i] += c[i - 1];
    for(int i = n; i >= 1; i--) sa[c[x[i]]--] = i;
    //基数排序
    for(int k = 1; k <= n; k<<= 1){
        int num = 0;
        for(int i = n - k + 1; i <= n; i++) y[++num] = i;
        rep(i, 1, n) if(sa[i] > k) y[++num] = sa[i] - k;
        rep(i, 0, m) c[i] = 0;
        rep(i, 1, n) c[x[i]]++;
        rep(i, 2, m) c[i] += c[i - 1];
        for(int i = n; i >= 1; i--) sa[c[x[y[i]]]--] = y[i], y[i] = 0;
        rep(i, 0, n){
            tt = x[i];
            x[i] = y[i];
            y[i] = tt; 
        }
        x[sa[1]] = 1, num = 1;
        rep(i, 2, n) x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k]) ? num : ++num;
        if(num == n) break;
        m = num;
    }
}
void get_height(){
    int k = 0;
    rep(i, 1, n) rk[sa[i]] = i;
    rep(i, 1, n){
        if(rk[i] == 1) continue;
        if(k) k--;//h【i】 >= h[i - 1] - 1
        int j = sa[rk[i] - 1];
        while(j + k <= n && i + k <= n && s[j + k] == s[i + k]) k++;
        height[rk[i]] = k;
    }
}

int main(){
    int t; scanf("%d",&t);
    while(t--){
        scanf("%s", s + 1);
        m = 122, n = strlen(s + 1);
        get_sa();
        get_height();
        int ans = (n+1)*n / 2;
        rep(i, 2, n) ans -= height[i];
        printf("%d
",ans);
    }
    return 0;
}
View Code

3  POJ - 2406:给出一个串,是由它的一个子串重复k次得到的,问k最大是多少。https://vjudge.net/contest/283743#problem/G

思路:第一种方法直接用next数组,求过next数组后直接输出 if(len % (len - ne[len]) == 0) printf("%d ",len / (len-ne[len])); else printf("1 ");

第二种就是后缀数组,自己没写,不加了。

4 POJ - 2774:求两个串的最长公共子串。 https://vjudge.net/contest/283743#problem/J

思路:先用个分隔符将两个字符串连起来,对这个新串跑一遍后缀数组,找最大的height值,注意还要满足条件:sa[i]与sa[i-1]分别在两串字符中,这样就保证height是两个串的公共前缀。

技术图片
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#define ll long long
#define rep(i, a, b) for(int i = a; i <= b; i++)
#define pb push_back
#define mp make_pair
using namespace std;

const int mod = 1e9 + 7;
const ll INF = 1e15;
const double eps = 1e-8;
const int maxn = 2e5 + 10;

int sa[maxn],rk[maxn], height[maxn], h[maxn], y[maxn], x[maxn], c[maxn];
int n, m;
char s[maxn];
void get_sa(){
    int tt;
    rep(i, 0, m) c[i] = 0;
    rep(i, 1, n) c[x[i] = s[i]]++;
    rep(i, 2, m) c[i] += c[i - 1];
    for(int i = n; i >= 1; i--) sa[c[x[i]]--] = i;
    //基数排序
    for(int k = 1; k <= n; k<<= 1){
        int num = 0;
        for(int i = n - k + 1; i <= n; i++) y[++num] = i;
        rep(i, 1, n) if(sa[i] > k) y[++num] = sa[i] - k;
        rep(i, 0, m) c[i] = 0;
        rep(i, 1, n) c[x[i]]++;
        rep(i, 2, m) c[i] += c[i - 1];
        for(int i = n; i >= 1; i--) sa[c[x[y[i]]]--] = y[i], y[i] = 0;
        rep(i, 0, n){
            tt = x[i];
            x[i] = y[i];
            y[i] = tt; 
        }
        x[sa[1]] = 1, num = 1;
        rep(i, 2, n) x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k]) ? num : ++num;
        if(num == n) break;
        m = num;
    }
}
void get_height(){
    int k = 0;
    rep(i, 1, n) rk[sa[i]] = i;
    rep(i, 1, n){
        if(rk[i] == 1) continue;
        if(k) k--;//h【i】 >= h[i - 1] - 1
        int j = sa[rk[i] - 1];
        while(j + k <= n && i + k <= n && s[j + k] == s[i + k]) k++;
        height[rk[i]] = k;
    }
}
char st[maxn];
int main(){
    scanf("%s %s",s+1,st+1);
    int len1 = strlen(s+1);
    int len2 = strlen(st+1);
    n = len1+len2+1;
    s[len1+1] = #;
    rep(i, 1, len2){
        s[len1+i+1] = st[i];
    }
    // s[len1+len2+2] = 0;
    n = strlen(s+1);
    m = 122;
    get_sa();
    get_height();
    int l, r;
    int ma = 0;
    rep(i, 2, n){
        l = sa[i-1];
        r = sa[i];
        if(l <= len1 && r <= len1) continue;
        else if(l > len1+1 && r > len1+1) continue;
        else ma = max(ma, height[i]);
    }
    printf("%d
",ma);
    return 0;
}
View Code

5  POJ - 1743:  https://vjudge.net/contest/283743#problem/C    不知道为啥当初学后缀数组时上来先写的这个,看了好久,现在思考过上边的几题后再想这个也很简单。

题意:有N(1 <= N <=20000)个音符的序列来表示一首乐曲,每个音符都是1~88范围内的整数,现在要找一个最长的重复主题。“主题”是整个音符序列的一个子串,它需要满足如下条件:

1、长度至少为5个音符。

2、在乐曲中重复出现。(可能经过转调,“转调”的意思是主题序列中每个音符都被加上或减去了同一个整数值)

3、重复出现的同一主题不能有公共部分。

思路:这题需要解决的问题有:重复子串的长度,重复部分不能有重合,转调。先把会的先想出来,重复子串的长度直接用height判断,不能有重合直接用sa判断位置是否有重合。就差这个转调了。

序列每个音符都加上或者减去同一个整数值,这不得不想到差分,这样就很容易想到我们只需要把数组预处理成差分就行了,因为s[i] - s[i-1]可能有负数,这样求sa就会有问题,都加上个数让数组不为负就行了,因为这个最大是88,差值不会超过88,所以我就加了个88,不用纠结于此。以上问题都解决了,怎么得到最长的?肯定是二分啊,明显满足二分属性。

技术图片
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#define ll long long
#define rep(i, a, b) for(int i = a; i <= b; i++)
#define pb push_back
#define mp make_pair
using namespace std;

const int mod = 1e9 + 7;
const ll INF = 1e15;
const double eps = 1e-8;
const int maxn = 1e5 + 10;

int sa[maxn],rk[maxn], height[maxn], h[maxn], y[maxn], x[maxn], c[maxn];
//rk[i] 第i个后缀的排名; SA[i] 排名为i的后缀位置; Height[i] 排名为i的后缀与排名为(i-1)的后缀的LCP
int n, m;
int s[maxn];
void get_sa(){
    int tt;
    rep(i, 0, n) c[i] = 0;
    rep(i, 1, n) c[x[i] = s[i]]++;
    rep(i, 2, m) c[i] += c[i - 1];
    for(int i = n; i >= 1; i--) sa[c[x[i]]--] = i;
    //基数排序
    for(int k = 1; k <= n; k<<= 1){
        int num = 0;
        for(int i = n - k + 1; i <= n; i++) y[++num] = i;
        rep(i, 1, n) if(sa[i] > k) y[++num] = sa[i] - k;
        rep(i, 0, m) c[i] = 0;
        rep(i, 1, n) c[x[i]]++;
        rep(i, 2, m) c[i] += c[i - 1];
        for(int i = n; i >= 1; i--) sa[c[x[y[i]]]--] = y[i], y[i] = 0;
        rep(i, 0, n){
            tt = x[i];
            x[i] = y[i];
            y[i] = tt; 
        }
        x[sa[1]] = 1, num = 1;
        rep(i, 2, n) x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k]) ? num : ++num;
        if(num == n) break;
        m = num;
    }
}
void get_height(){
    int k = 0;
    rep(i, 1, n) rk[sa[i]] = i;
    rep(i, 1, n){
        if(rk[i] == 1) continue;
        if(k) k--; 
        int j = sa[rk[i] - 1];
        while(j + k <= n && i + k <= n && s[j + k] == s[i + k]) k++;
        height[rk[i]] = k;
    }
}

bool judge(int k){
    int ma = sa[1], mi = sa[1];
    rep(i, 2, n){
        if(height[i] < k) mi = ma = sa[i];
        else{
            mi = min(sa[i], mi);
            ma = max(sa[i], ma);
            if(ma - mi + 1 >= k) return 1;
        }
    }
    return 0;
}

int main(){
    while(scanf("%d",&n) && n){
        rep(i, 1, n) scanf("%d",&s[i]);
        m = 0;
        rep(i, 1, n-1){
            s[i] = s[i+1] - s[i] + 88;
            m = max(m, s[i]);
        }
        s[n] = 0; n--;
        get_sa();
        get_height();
        int l = 4, r = n/2;
        int ans = 0;
        while(l <= r){
            int mid = (l+r) / 2;
            if(judge(mid)){
                ans = mid;
                l = mid+1;
            }
            else r = mid-1;
        }
        // cout << ans << endl;
        if(ans >= 4) printf("%d
",ans+1);
        else printf("0
");
    }
    return 0;
}
View Code

 

以上是关于后缀数组模板题总结的主要内容,如果未能解决你的问题,请参考以下文章

后缀数组 模板题 hdu1403(最长公共(连续)子串)

后缀自动机 模板题

PKU 2774 Long Long Message (后缀数组练习模板题)

后缀数组刷题总结

SPOJ DISUBSTR ——后缀数组

后缀数组