后缀数组和自动机

后缀数组

算法思路:倍增+基数排序
数组:sa(第i小的后缀的下标),rk(i后缀是第几小)。
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

JSOI2007 字符加密(排序循环移动位置)
题意:给出一个串abcde。他有多个循环移动位置:abcde,bcdea,cdeab,…,ebacd。将这些排序。再按从小到大的顺序输出每个串的最后一个字母。
思路:将字串复制为abcdeabcde后计算后缀数组。

#include<bits/stdc++.h>

using namespace std;

const int N = 2e5+5;

char s[N];
int n,sa[N],rk[N],oldrk[N<<1],id[N],px[N],cnt[N];

bool cmp(int x,int y,int w){
    return oldrk[x] == oldrk[y] && oldrk[x+w] == oldrk[y+w];
}

int main() {
    int i,m = 200,p,w;
    scanf("%s",s+1);
    n = strlen(s+1);
    for(i=1;i<=n;++i) s[i+n]=s[i];
    n*=2;
    for(i=1;i<=n;++i) ++cnt[rk[i]=s[i]];
    for(i=1;i<=m;++i) cnt[i] += cnt[i-1];
    for(i=n;i>=1;--i) sa[cnt[rk[i]]--] = i;

    for(w=1;;w<<=1,m=p){
        for(p=0,i=n;i>n-w;--i) id[++p] = i;
        for(i=1;i<=n;++i) if(sa[i] > w) id[++p] = sa[i] - w;
        memset(cnt,0,sizeof(cnt));
        for(i=1;i<=n;++i) ++cnt[px[i]=rk[id[i]]];
        for(i=1;i<=m;++i) cnt[i] += cnt[i-1];
        for(i=n;i>=1;--i) sa[cnt[px[i]]--] = id[i];
        memcpy(oldrk,rk,sizeof(rk));
        for(p=0,i=1;i<=n;++i)
            rk[sa[i]] = cmp(sa[i],sa[i-1],w) ? p: ++p;
        if(p==n){
            for(int i=1;i<=n;++i) sa[rk[i]] = i;
            break;
        }
    }

    for(i=1;i<=n;++i){
        if(sa[i] > n/2) continue;
        printf("%c",s[sa[i] + n/2 - 1]);
    }
    putchar(10);
}

[USACO06DEC]Milk Patterns(出现次数大于k的最长子串)
思路:
子串是后缀的前缀。
因此我们先进行后缀排序。排序后相邻的k个后缀的最长公共前缀长度即是答案。k个后缀的最长公共前缀长度就是k-1个连续height数组。用单调队列维护答案。

#include<bits/stdc++.h>

using namespace std;

const int N = 2e4+5;

int s[N];
int n,ht[N],sa[N],rk[N],oldrk[N<<1],id[N],px[N],cnt[1000002];

bool cmp(int x,int y,int w){
    return oldrk[x] == oldrk[y] && oldrk[x+w] == oldrk[y+w];
}

int main() {
    int i,m=1000000,p,w,k;
    scanf("%d%d",&n,&k);k--;
    for(i=1;i<=n;++i) scanf("%d",&s[i]);
    // 后缀排序
    for(i=1;i<=n;++i) ++cnt[rk[i]=s[i]];
    for(i=1;i<=m;++i) cnt[i] += cnt[i-1];
    for(i=n;i>=1;--i) sa[cnt[rk[i]]--] = i;

    for(w=1;;w<<=1,m=p){
        for(p=0,i=n;i>n-w;--i) id[++p] = i;
        for(i=1;i<=n;++i) if(sa[i] > w) id[++p] = sa[i] - w;
        memset(cnt,0,sizeof(cnt));
        for(i=1;i<=n;++i) ++cnt[px[i]=rk[id[i]]];
        for(i=1;i<=m;++i) cnt[i] += cnt[i-1];
        for(i=n;i>=1;--i) sa[cnt[px[i]]--] = id[i];
        memcpy(oldrk,rk,sizeof(rk));
        for(p=0,i=1;i<=n;++i)
            rk[sa[i]] = cmp(sa[i],sa[i-1],w) ? p: ++p;
        if(p==n){
            for(int i=1;i<=n;++i) sa[rk[i]] = i;
            break;
        }
    }

    for(i=1,p=0;i<=n;++i){
        if(p) --p;
        while(s[i+p]==s[sa[rk[i]-1]+p]) ++p;
        ht[rk[i]] = p;
    }

    // 单调队列维护长度为k的区间的height最小值的最大值
    // 那么就维护一个递增的数列
    int ans = 0;
    deque<int> q;
    for(i=1;i<=n;++i){
        while(q.size() && q.front() <= i-k) q.pop_front();
        while(q.size() && ht[q.back()] >= ht[i]) q.pop_back();
        q.push_back(i);
        if(i>=k) ans = max(ans,ht[q.front()]);
    }
    printf("%d\n",ans);
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值