【BZOJ】5417: [Noi2018]你的名字 -后缀自动机&线段树可持久化合并

传送门:bzoj5417


题解

首先还是那句话,要清楚后缀自动机的本质(后缀自动机详解)。

S S S建一个后缀自动机,每次对 T T T同样建一个后缀自动机。

考虑 l = 1 , r = ∣ S ∣ l=1,r=|S| l=1,r=S的情况:

l i m i lim_i limi表示字符串 T T T区间 [ 1 , i ] [1,i] [1,i]所能匹配 S S S的最长后缀 [ i − l i m i + 1 , i ] ( l i m i > 0 ) [i-lim_i+1,i](lim_i>0) [ilimi+1,i](limi>0)(若 l i m i = 0 lim_i=0 limi=0,则 T i T_i Ti字符没有在 S S S中出现)。 l i m i lim_i limi可以同步在 S S S的后缀自动机不断向下扩展得到,当无法匹配时,不断跳 f a i l fail fail链直到可以匹配上,保证匹配的是字符串 T [ 1 , i ] T[1,i] T[1,i]的后缀。

假设对于 S S S所构成的自动机,节点 i i i r i g h t right right集合中所能表示的字符串最大长度为 m x i mx_i mxi t a g i tag_i tagi表示该 r i g h t right right集合字符串第一次出现的位置(这些字符串是后缀包含关系,末位置相同),节点 i i i f a i l fail fail链指向节点 f l i fl_i fli

则: a n s = ∑ i = 2 c n t m a x ( 0 , m x i − m a x ( m x f l i , l i m t a g i ) ) ans=\sum \limits_{i=2}^{cnt} max(0,mx_i-max(mx_{fl_i},lim_{tag_i})) ans=i=2cntmax(0,mximax(mxfli,limtagi))

实际上我们只需要在 S S S的后缀自动机上移动,最后 O ( c n t ) O(cnt) O(cnt)枚举一遍在 T T T的后缀自动机上统计答案( c n t cnt cnt T T T后缀自动机上节点个数)。
1 1 1号节点为后缀自动机起始节点。

再考虑 l ≠ 1 , r ≠ ∣ S ∣ l\neq 1,r\neq |S| l̸=1,r̸=S的情况:

稍有区别的是,在处理 l i m i lim_i limi时,需要判断当前的字符串是否在 S S S [ l , r ] [l,r] [l,r]区间中出现,可以先对 S S S的后缀自动机每个节点按 m x i mx_i mxi排序,以每个节点建立线段树表示出现位置,再不断向 f l i fl_i fli合并。

每次在 S S S的后缀自动机往下扩展时,判断 [ r t [ p o s ] , l + l e n , r ] [rt[pos],l+len,r] [rt[pos],l+len,r]中是否出现即可( p o s pos pos表示当前移动到的后缀自动机节点, l e n len len表示当前匹配了 T T T [ i − l e n + 1 , i ] [i-len+1,i] [ilen+1,i],询问 [ l + l e n , r ] [l+len,r] [l+len,r]区间的出现位置才能保证字符串出现在 [ l , r ] [l,r] [l,r]内)。
O ( ∣ S ∣ + l o g ∣ S ∣ + ∑ ( ∣ T ∣ + ∣ T ∣ l o g ∣ S ∣ ) ) O(|S|+log|S|+\sum(|T|+|T|log|S|)) O(S+logS+(T+TlogS))


代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+100,M=2e7+100,inf=0x3f3f3f3f;
int que,n,m,rt[N],lim[N];
char S[N],T[N];ll ans;

namespace tree{
    #define mid (((l)+(r))>>1)
    #define lc ch[k][0]
    #define rc ch[k][1]
    int tot,ch[M][2];
    
    inline void ins(int &k,int l,int r,int pos)
    {
        if(!k) k=++tot;
        if(l==r) return;
        if(pos<=mid) ins(lc,l,mid,pos);
        else ins(rc,mid+1,r,pos);
    }
    
    inline int merge(int x,int y)
    {
        if(!x || !y) return x+y;
        int k=++tot;
        lc=merge(ch[x][0],ch[y][0]);
        rc=merge(ch[x][1],ch[y][1]);
        return k;
    }
    
    inline bool query(int k,int l,int r,int L,int R)
    {
        if(!k || L>R) return false;
        if(L<=l && r<=R) return true;
        if(L<=mid) if(query(lc,l,mid,L,R)) return true;
        if(R>mid) if(query(rc,mid+1,r,L,R)) return true;
        return false; 
    }
    
}

namespace SAM{
   int p,q,cnt=1,cur=1;
   int ch[N][26],mx[N],fl[N],cc[N],sa[N],in[N];
   
   inline void insert(int alp,int id)
   {
   	   p=cur;cur=++cnt;mx[cur]=id;in[cur]=1;
   	   for(;!ch[p][alp] && p;p=fl[p]) ch[p][alp]=cur;
   	   if(!p) fl[cur]=1;else{
   	   	  q=ch[p][alp];
   	   	  if(mx[q]==mx[p]+1) fl[cur]=q;else{
   	   	  	mx[++cnt]=mx[p]+1;
   	   	  	memcpy(ch[cnt],ch[q],sizeof(ch[q]));
   	   	  	fl[cnt]=fl[q];fl[cur]=fl[q]=cnt;
   	   	  	for(;ch[p][alp]==q;p=fl[p]) ch[p][alp]=cnt;
   	   	  }
   	   }
   }
   
   inline void build()
   {
   	   register int i,j;
       for(i=1;i<=n;++i) insert(S[i]-'a',i);
       for(i=1;i<=cnt;++i) cc[mx[i]]++;
       for(i=1;i<=n;++i) cc[i]+=cc[i-1];
       for(i=cnt;i>1;--i) sa[cc[mx[i]]--]=i;
       for(i=cnt;i>1;--i){
       	  j=sa[i];
          if(in[j]) tree::ins(rt[j],1,n,mx[j]);	 
          rt[fl[j]]=tree::merge(rt[fl[j]],rt[j]);
       }
   }
   
}

namespace solve{
     int p,q,cur=1,cnt=1;
     int ch[N][26],mx[N],fl[N],tag[N];
     
     inline void clr()
     {
     	register int i,j;
     	for(i=0;i<=cnt;++i) memset(ch[i],0,sizeof(ch[i]));
     	cur=cnt=1;
     }
     
     inline void ins(int alp,int id)
     {
     	 p=cur;cur=++cnt;mx[cur]=id;tag[cur]=id;
     	 for(;!ch[p][alp] && p;p=fl[p]) ch[p][alp]=cur;
     	 if(!p) fl[cur]=1;else{
     	 	q=ch[p][alp];
     	 	if(mx[q]==mx[p]+1) fl[cur]=q;else{
     	 		mx[++cnt]=mx[p]+1;tag[cnt]=tag[q];
     	 		memcpy(ch[cnt],ch[q],sizeof(ch[q]));
     	 		fl[cnt]=fl[q];fl[q]=fl[cur]=cnt;
     	 		for(;ch[p][alp]==q;p=fl[p]) ch[p][alp]=cnt;
     	 	}
     	 }
     }
     
 	 inline ll work()
     {
  	    register int i,j,L,R,len,u,alp;
        scanf("%s%d%d",T+1,&L,&R);
        clr();
        m=strlen(T+1);
        for(len=0,u=1,i=1;i<=m;++i){
          alp=T[i]-'a';
          ins(alp,i);
          for(;;){
              if(SAM::ch[u][alp] && tree::query(rt[SAM::ch[u][alp]],1,n,L+len,R)){
                 len++;
                 u=SAM::ch[u][alp];
                 break;
              }
              if(len==0) break;
              len--;
              if(len==SAM::mx[SAM::fl[u]]) u=SAM::fl[u];
          }
      	  lim[i]=len;	
        }
        for(ans=0,i=2;i<=cnt;++i)
         ans+=max(0,mx[i]-max(mx[fl[i]],lim[tag[i]]));
        return ans;
    }
}

inline void out(ll x){if(x>9) out(x/10);putchar('0'+x%10);}

int main(){
    scanf("%s",S+1);
    n=strlen(S+1);
    SAM::build();
    scanf("%d",&que);
    for(;que;--que) out(solve::work()),puts("");
    return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值