[HAOI2017]供给侧改革

题目

这道题我们其实就是利用了一棵后缀树

由于字符串是随机的,所以这个后缀树的树高是\(log\)的,基于树高的算法是能过的

我们考虑后缀树上的两个节点的\(lca\)就是这两个节点所代表的后缀的\(lcp\)

我们可以把询问按照右端点离线,每次都暴力跳这个在后缀树上暴力跳父亲

我们维护一下后缀树上子树里的最大值,这样我们就可以维护出对于每一种\(lcp\)出现最靠后的位置在哪里了

对于每次询问我们扫一遍这些个\(lcp\)就好了

至于如何建一棵后缀树,自然是把原串反着建一个SAM啊

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define re register
#define LL long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
inline int read() {
    char c=getchar();int x=0;while(c<'0'||x>'9') c=getchar();
    while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
const int maxn=2e5+5;
struct Ask{int l,r,rk;}q[maxn>>1];
int cnt=1,lst=1,len[maxn],son[maxn][2],fa[maxn];
int pos[maxn>>1],lcp[maxn>>1],mx[maxn],g[maxn>>1],h[maxn>>1];
int n,m,T;LL Ans[maxn>>1];char S[maxn>>1];
inline int cmp(Ask A,Ask B) {return A.r<B.r;}
inline void Extend(int c,int o) {
    int p=++cnt,f=lst;lst=p;
    len[p]=len[f]+1,pos[o]=p;
    while(f&&!son[f][c]) son[f][c]=p,f=fa[f];
    if(!f) {fa[p]=1;return;}
    int x=son[f][c];
    if(len[f]+1==len[x]) {fa[p]=x;return;}
    int y=++cnt;
    len[y]=len[f]+1,fa[y]=fa[x],fa[x]=fa[p]=y;
    son[y][0]=son[x][0],son[y][1]=son[x][1];
    while(f&&son[f][c]==x) son[f][c]=y,f=fa[f];
}
inline void ins(int i) {
    int x=pos[i];
    while(x) {
        lcp[len[x]]=max(lcp[len[x]],mx[x]);
        if(lcp[len[x]]) T=max(T,len[x]);
        mx[x]=max(mx[x],i);x=fa[x];
    }
}
inline void calc(int j) {
    int t=q[j].r-1,tmp=0;
    LL ans=0;
    for(re int i=1;i<=T;++i) 
    if(lcp[i]) {
        while(lcp[i]>=g[tmp]&&tmp) tmp--;
        g[++tmp]=lcp[i],h[tmp]=i;
    }
    g[tmp+1]=0;
    for(re int i=2;i<=tmp+1;i++) {
        if(g[i]<q[j].l) {
            ans+=(g[i-1]-q[j].l+1)*h[i-1];
            break;
        }
        ans+=(g[i-1]-g[i])*h[i-1];
    }
    Ans[q[j].rk]=ans;
}
int main() {
    n=read(),m=read();scanf("%s",S+1);
    for(re int i=1;i<=m;i++) q[i].l=read(),q[i].r=read(),q[i].rk=i;
    for(re int i=n;i;--i) Extend(S[i]-'0',i);
    std::sort(q+1,q+m+1,cmp);ins(1);int tot=1;
    for(re int i=2;i<=n;i++) {
        ins(i);
        while(q[tot].r==i) calc(tot++);
    }
    for(re int i=1;i<=m;i++) printf("%lld\n",Ans[i]);
    return 0;
}

转载于:https://www.cnblogs.com/asuldb/p/10792291.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值