bzoj4516 后缀数组/后缀自动机

本文介绍了一种解决字符串问题的有效方法——后缀数组。通过一个具体的模板题,详细讲解了如何利用后缀数组来计算字符串前缀中不同子串的数量。文章提供了完整的代码实现,并解释了关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意(后缀数组模板题)
给你一个字符串,问该字符串的每一个前缀中分别有多少个不同的子串。
n<=100000

这道题目最简单粗暴的方法是建立sam,然后每个点用一个map保存,统计答案就用len[i]-len[fa[i]]即可。。

考虑后缀数组。将整个数组反过来,那么每次就相当于查询后缀[i,n]中有多少本质不同的子串。那么就可以建出后缀数组,然后新加入一个后缀[p],那么首先需要得到这个后缀中有多少子串是重复的,相当于求这个后缀与后缀集合[p+1,n]的最长lcp。由于已经得到了后缀数组,只需要知道后缀[p]在[p+1,n]中相邻的两个后缀(树状数组维护,非常巧妙,查询前后缀max,并将后缀下标转成前缀),然后利用rmq求任意后缀的lcp即可。
注意开long long。(from lych)

#include<bits/stdc++.h>
using namespace std;
#define maxn 100020
#define rep(i,l,r) for (register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define lowbit(x) (x&(-x))

typedef long long ll;
int s[maxn],c[maxn],t1[maxn * 2],t2[maxn * 2],sa[maxn],rk[maxn],h[maxn];
int n,a[maxn];
int mn[20][maxn * 2],cnt[maxn];
int mx[2][maxn];
ll ans[maxn];

//sa[i]表示排名为i的后缀的位置
    //rk[i]表示第i个后缀的排名
    //x[i]表示i的排名(第一关键字)
    //y[i]表示第二关键字排名为i的后缀的起始位置
    //h[i]表示后缀i在排序后与前一位的lcp
    //求i,j的lcp是排序后rk[i],rk[j],height的min

void suffix_array(){
    int m = n , *x = t1 , *y = t2;
    rep(i,0,m) c[i] = 0;
    rep(i,0,n - 1) c[x[i] = s[i]]++;
    rep(i,1,m) c[i] += c[i - 1];
    rep(i,0,n - 1) sa[--c[x[i]]] = i;
//  rep(i,1,n) cout<<sa[i - 1]<<" ";
//  cout<<endl;;    
    for (register int k = 1 ; k < n ; k <<= 1){
        register int p = 0;
        memset(y,0,sizeof(t1));
        repd(i,n - 1,n - k) y[p++] = i; //必须倒着for,在后面的位置更小
        rep(i,0,n - 1) if ( sa[i] >= k ) y[p++] = sa[i] - k;

        rep(i,0,m) c[i] = 0;
        rep(i,0,n - 1) c[x[y[i]]]++;
        rep(i,1,m) c[i] += c[i - 1];
        repd(i,n - 1,0) sa[--c[x[y[i]]]] = y[i];

        p = 0 , swap(x,y) , x[sa[0]] = ++p;
        rep(i,1,n - 1) x[sa[i]] = (y[sa[i]] == y[sa[i - 1]]) && (y[sa[i] + k] == y[sa[i - 1] + k]) ? p : ++p;
        if ( p >= n ) break; //如果当前已经完成排名,则break
        m = p;
    }
    rep(i,0,n - 1) rk[sa[i]] = i;
    int k = 0;
    rep(i,0,n - 1){
        if ( !rk[i] ) continue;
        int j = sa[rk[i] - 1];
        if ( k ) k--;
        while ( s[j + k] == s[i + k] ) k++;
        h[rk[i]] = k;
    }   
/*  rep(i,0,n - 1) cout<<sa[i]<<" "<<h[i]<<endl;;
    cout<<endl;;
    rep(i,0,n - 1) cout<<rk[i]<<" ";
    cout<<endl;*/
}
void pre(){
    sort(a,a + n);
    rep(i,0,n - 1) s[i] = lower_bound(a,a + n,s[i]) - a + 1;
    reverse(s,s + n);
}
//求两个后缀的最长公共前缀
//在后缀排序后的h数组中用区间RMQ查最小值
void init(){
    int k = 0;
    rep(i,0,n){
        if ( i > (1 << (k + 1)) ) k++;
        cnt[i] = k;
    }
    suffix_array();
    rep(i,0,n - 1) mn[0][i] = h[i];
    rep(i,1,17)
        rep(j,0,n - 1)
            mn[i][j] = min(mn[i - 1][j],mn[i - 1][j + (1 << (i - 1))]);
}
inline int lcp(int x,int y){
    if ( y == -1 || y >= n ) return 0;
    if ( x > y ) swap(x,y);
    x++;
    int c = cnt[y - x + 1];
    return min(mn[c][x],mn[c][y - (1 << c) + 1]);
}
//==============================================================================
inline int query(int t,int id){
    int res = -1;
    for (int i = id ; i ; i -= lowbit(i)) res = max(res,mx[t][i]);
    return res;
}
inline void insert(int t,int id){
    for (int i = id ; i <= n ; i += lowbit(i)) mx[t][i] = max(mx[t][i],id);
}
int main(){
    freopen("input.txt","r",stdin);
    scanf("%d",&n);
    rep(i,0,n - 1) scanf("%d",&s[i]) , a[i] = s[i];
    pre();
    init();
    repd(i,n - 1,0){
        int t1 = query(0,rk[i]) - 1 , t2 = n - query(1,n - rk[i] - 1);
        ans[i] = ans[i + 1] + (n - i) - max(lcp(rk[i],t1),lcp(rk[i],t2));
        insert(0,rk[i] + 1) , insert(1,n - rk[i]);
    }
    repd(i,n - 1,0) printf("%lld\n",ans[i]);
    return 0;
}


暂时不会线性后缀数组,这份代码编译有问题,留坑

 //后缀数组 -线性
#include<bits/stdc++.h>
#define N 100020
#define fill_n(a,n,d) memset(a,d,sizeof(a)); 
 namespace SA {
 int sa[N], rk[N], ht[N], s[N<<1], t[N<<1], p[N], cnt[N], cur[N];
 #define pushS(x) sa[cur[s[x]]--] = x
 #define pushL(x) sa[cur[s[x]]++] = x
 #define inducedSort(v) fill_n(sa, n, -1); fill_n(cnt, m, 0); \
 for (int i = 0; i < n; i++) cnt[s[i]]++; \
 for (int i = 1; i < m; i++) cnt[i] += cnt[i-1]; \
 for (int i = 0; i < m; i++) cur[i] = cnt[i]-1; \
 for (int i = n1-1; ~i; i--) pushS(v[i]); \
 for (int i = 1; i < m; i++) cur[i] = cnt[i-1]; \
 for (int i = 0; i < n; i++) if (sa[i] > 0 && t[sa[i]-1]) pushL(sa[i]-1); \
 for (int i = 0; i < m; i++) cur[i] = cnt[i]-1; \
 for (int i = n-1; ~i; i--) if (sa[i] > 0 && !t[sa[i]-1]) pushS(sa[i]-1)
 void sais(int n, int m, int *s, int *t, int *p) {
 int n1 = t[n-1] = 0, ch = rk[0] = -1, *s1 = s+n;
 for (int i = n-2; ~i; i--) t[i] = s[i] == s[i+1] ? t[i+1] : s[i] > s[i+1];
 for (int i = 1; i < n; i++) rk[i] = t[i-1] && !t[i] ? (p[n1] = i, n1++) : -1;
 inducedSort(p);
 for (int i = 0, x, y; i < n; i++) if (~(x = rk[sa[i]])) {
 if (ch < 1 || p[x+1] - p[x] != p[y+1] - p[y]) ch++;
 else for (int j = p[x], k = p[y]; j <= p[x+1]; j++, k++)
 if ((s[j]<<1|t[j]) != (s[k]<<1|t[k])) {
 ch++;
 break;
 }
 s1[y = x] = ch;
 }
 if (ch+1 < n1) sais(n1, ch+1, s1, t+n, p+n1);
 else for (int i = 0; i < n1; i++) sa[s1[i]] = i;
 for (int i = 0; i < n1; i++) s1[i] = p[sa[i]];
 inducedSort(s1);
 }
 template<typename T>
 int mapCharToInt(int n, const T *str) {
 int m = *max_element(str, str+n);
 fill_n(rk, m+1, 0);
 for (int i = 0; i < n; i++) rk[str[i]] = 1;
 for (int i = 0; i < m; i++) rk[i+1] += rk[i];
 for (int i = 0; i < n; i++) s[i] = rk[str[i]] - 1;
 return rk[m];
 }
 // Ensure that str[n] is the unique lexicographically smallest character in str.
 template<typename T>
 void suffixArray(int n, const T *str) {
 int m = mapCharToInt(++n, str);
 sais(n, m, s, t, p);
 for (int i = 0; i < n; i++) rk[sa[i]] = i;
 for (int i = 0, h = ht[0] = 0; i < n-1; i++) {
 int j = sa[rk[i]-1];
 while (i+h < n && j+h < n && s[i+h] == s[j+h]) h++;
 if (ht[rk[i]] = h) h--;
 }
 }
 };

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值