[Bzoj1396]识别子串

本文介绍如何利用后缀自动机(SAM)求解在原字符串中仅出现一次的子串及其最短长度问题。文章详细解释了通过SAM树找到只出现一次的子串的方法,并给出完整的C++代码实现。

题意

i[1,n]∀i∈[1,n]求包含ii并且在原串s中只出现一次的子串的最短长度


题解

因为每个点parentparent树上的父亲肯定是当前节点所代表的串的后缀

那么只出现一次的串就是parentparent树上的叶子节点

考虑这个点所代表的len[i]len[fa[i]]len[i]−len[fa[i]]个子串

L=len[i]len[fa[i]],R=len[i]L=len[i]−len[fa[i]],R=len[i]

1.i[L,R],ans[i]=min{len[fa[i]]+1}=min{RL+1}1.∀i∈[L,R],ans[i]=min{len[fa[i]]+1}=min{R−L+1}

因为父亲节点所代表的串都在叶子里出现过了,所以最短没出现的子串长度就是父亲的长度加11

2.i[1,L),S[i,R]这个子串出现次数也是11

那么ans[i]=min{Ri+1}=min{R+1}i

上面的东西涉及到区间修改和区间求最小值,用两颗线段树维护一下就好了

#include<bits/stdc++.h>
#define fp(i,a,b) for(register int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(register int i=a,I=b-1;i>I;--i)
#define go(u) for(register int i=fi[u],v=e[i].to;i;v=e[i=e[i].nx].to)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
char sr[1<<21],z[20];int C=-1,Z;
inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
template<class T>inline void we(T x){
    if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x;
    while(z[++Z]=x%10+48,x/=10);
    while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
const int N=1e5+5,M=2*N,inf=1061109567;
typedef long long ll;
typedef int arr[M];
int n,k;char s[N];string str[N];
struct seg{
    int tr[N<<2];
    #define lc p<<1
    #define rc p<<1|1
    seg(){memset(tr,63,sizeof tr);}
    inline void cov(int p,int w){cmin(tr[p],w);}
    inline void down(int p){if(tr[p]^inf)cov(lc,tr[p]),cov(rc,tr[p]),tr[p]=inf;}
    void mdy(int p,int L,int R,int a,int b,int w){
        if(a>b)return;
        if(a<=L&&R<=b)return cov(p,w);
        down(p);int mid=(L+R)>>1;
        if(a<=mid)mdy(lc,L,mid,a,b,w);
        if(b>mid)mdy(rc,mid+1,R,a,b,w);
    }
    int qry(int p,int L,int R,int a){
        if(L==R)return tr[p];
        down(p);int mid=(L+R)>>1;
        if(a<=mid)return qry(lc,L,mid,a);
        return qry(rc,mid+1,R,a);
    }
    #undef lc
    #undef rc
}t1,t2;
struct SAM{
    int las,T,ch[M][26];arr fa,len;
    SAM(){las=T=1;}
    inline void ins(int c){
        int p=las,np;fa[las=np=++T]=1,len[np]=len[p]+1;
        for(;p&&!ch[p][c];p=fa[p])ch[p][c]=np;
        if(p){
            int q=ch[p][c],nq;
            if(len[p]+1==len[q])fa[np]=q;
            else{
                fa[nq=++T]=fa[q],len[nq]=len[p]+1,memcpy(ch[nq],ch[q],4*26);
                for(fa[q]=fa[np]=nq;ch[p][c]==q;p=fa[p])ch[p][c]=nq;
            }
        }
    }arr fg;
    inline void sol(int n){
        fp(i,1,T)fg[fa[i]]=1;
        fp(i,1,T)if(!fg[i]){
            int l=len[i]-len[fa[i]],r=len[i];
            t1.mdy(1,1,n,1,l-1,r+1);
            t2.mdy(1,1,n,l,r,r-l+1);
        }
        fp(i,1,n)we(min(t1.qry(1,1,n,i)-i,t2.qry(1,1,n,i)));
    }
}p;
int main(){
    #ifndef ONLINE_JUDGE
        file("s");
    #endif
    scanf("%s",s+1);
    fp(i,1,strlen(s+1))p.ins(s[i]-'a');
    p.sol(strlen(s+1));
return Ot(),0;
}
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值