P4770:你的名字(SAM、线段树合并)

本文介绍了如何使用Suffix Automaton (SAM)和线段树来高效地解决区间内字符串子串计数的问题。首先解释了如何在无区间限制的情况下求解,然后扩展到有区间限制的情况,通过在线段树上进行合并和查询操作。文章详细阐述了算法的实现过程,包括SAM的构建、匹配过程和线段树的更新。

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

文章目录

前言

1000A快乐!!!awa
没有想象中的那么恶心。

解析

先考虑每次都询问 [ 1 , n ] [1,n] [1,n] 如何做。
正难则反,用T所有本质不同串数量减去是S串子串又是T的子串的数量
前者很好求,关键是后者
首先可以常规操作求出每个T的前缀在S上匹配的最长后缀长度 w w w
考虑对T也建出一个SAM,让T在自己的SAM上跑
假设跑到第 i i i 个字符,处于结点 u u u
w i ≤ l e n l i n k u w_i\le len_{link_u} wilenlinku,说明在 u u u 结点的等价类集合中没有任何子串是S的子串,那么直接往 l i n k link link 跳即可,不断上跳,直到不满足这个条件
然后,令 a n s u ← max ⁡ ( a n s u , w i ) ans_u\gets \max(ans_u,w_i) ansumax(ansu,wi)
最后匹配子串的总和就是 ∑ ( a n s i − l e n l i n k i ) \sum (ans_i-len_{link_i}) (ansilenlinki)

考虑有区间限制 [ l , r ] [l,r] [l,r]
发现其实后面对于T的SAM的操作是不影响的,唯一的不同就是 w w w 不太好求了
考虑对S的SAM的每个结点建一棵线段树,存储 endpos ⁡ \operatorname{endpos} endpos 集合
dfs 一遍线段树合并就可以求出来
(注意这里需要保留原有的线段树,需要可持久化)
然后我们继续常规操作,在S的串上跑T试图求出 w i w_i wi
但是现在的答案还要与 max ⁡ i ∈ e n d p o s u ⁡ , i ≤ r i − l + 1 \max_{i\in \operatorname{endpos_u},i\le r}i-l+1 maxiendposu,iril+1 取个 min
(也就是合法右端点左侧最大的endpos,可以通过刚才建出的线段树查询)

那么我们再匹配之余看看往父亲跳是否可以不劣,如果不劣就往上,这样就可以了

注意跳父亲的条件是不劣而不是更优,因为可能需要跳多次 l i n k link link 才能跳到最优解,在此之前结果不变

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
const int N=1e6+100;
inline ll read(){
  ll x(0),f(1);char c=getchar();
  while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
  while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
  return x*f;
}

int m;

#define mid ((l+r)>>1)
struct tree{
  int ls,rs,mx;
};
struct Segment_tree{
  tree tr[N<<5];
  int rt[N],tot;
  inline int copy(int x){
    tr[++tot]=tr[x];return tot;
  }
  inline void pushup(int k){
    tr[k].mx=max(tr[tr[k].ls].mx,tr[tr[k].rs].mx);return;
  }
  void upd(int &k,int l,int r,int p){
    if(!k) k=copy(0);
    if(l==r){
      tr[k].mx=l;return;
    }
    if(p<=mid) upd(tr[k].ls,l,mid,p);
    else upd(tr[k].rs,mid+1,r,p);
    pushup(k);
  }
  int merge(int x,int y,int l,int r){
    if(!x||!y) return x|y;
    if(l==r) return x;
    int now=copy(x);
    tr[now].ls=merge(tr[now].ls,tr[y].ls,l,mid);
    tr[now].rs=merge(tr[now].rs,tr[y].rs,mid+1,r);
    pushup(now);
    return now;
  }
  int ask(int k,int l,int r,int x,int y){
    if(!k) return 0;
    if(x<=l&&r<=y) return tr[k].mx;
    int res(0);
    if(x<=mid) res=max(res,ask(tr[k].ls,l,mid,x,y));
    if(y>mid) res=max(res,ask(tr[k].rs,mid+1,r,x,y));
    return res;
  }
}t;

struct node{
  int len,fa;
  int tr[26];
};
struct SAM{
  char s[N];
  int n;
  node st[N];
  int cnt[N],id[N],pl[N];
  ll sum[N];
  int tot=1,lst=1;
  inline void clear(){
    while(tot){
      st[tot].len=st[tot].fa=0;
      memset(st[tot].tr,0,sizeof(st[tot].tr));sum[tot]=0;
      --tot;
    }
    tot=lst=1;
    return;
  }
  inline void ins(int c,int o){
    c-='a';
    int cur=++tot,p=lst;lst=tot;
    st[cur].len=st[p].len+1;pl[cur]=o;
    for(;p&&!st[p].tr[c];p=st[p].fa) st[p].tr[c]=cur;
    if(!st[p].tr[c]) st[cur].fa=1;
    else{
      int q=st[p].tr[c];
      if(st[q].len==st[p].len+1) st[cur].fa=q;
      else{
	int pp=++tot;st[pp]=st[q];
	st[pp].len=st[p].len+1;
	st[q].fa=st[cur].fa=pp;
	for(;p&&st[p].tr[c]==q;p=st[p].fa) st[p].tr[c]=pp;
      }
    }
  }
  void calc(){
    fill(cnt,cnt+1+n,0);
    for(int i=1;i<=tot;i++) ++cnt[st[i].len];
    for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1];
    for(int i=tot;i>=1;i--) id[cnt[st[i].len]--]=i;
    for(int i=tot;i>=1;i--){
      if(id[i]!=1) sum[id[i]]=1;
      for(int j=0;j<26;j++) sum[id[i]]+=sum[st[id[i]].tr[j]];
    }
    
    return;
  }
  void build(){
    scanf(" %s",s+1);n=strlen(s+1);
    for(int i=1;i<=n;i++) ins(s[i],i);
    return;
  }
  void print(){
    for(int i=1;i<=tot;i++)
      printf("i=%d fa=%d len=%d sum=%lld pl=%d\n",i,st[i].fa,st[i].len,sum[i],pl[i]);
  }
}s1,s2;

vector<int>v[N];
int ans[N];
void dfs(int x){
  if(s1.pl[x]){
    t.upd(t.rt[x],1,s1.n,s1.pl[x]);
  }
  for(int to:v[x]){
    dfs(to);
    t.rt[x]=t.merge(t.rt[x],t.rt[to],1,s1.n);
  }
  // printf("x=%d mx=%d\n",x,t.tr[t.rt[x]].mx);
  return;
}

int L,R;
inline int lenth(int x,int val){
  return max(0,min(min(val,s1.st[x].len),t.ask(t.rt[x],1,s1.n,1,R)-L+1));
}
void work(){
  s2.build();s2.calc();L=read();R=read();
  // s2.print();
  int p1=1,p2=1,now(0);
  for(int i=1;i<=s2.n;i++){
    int c=s2.s[i]-'a';
    while(p1>1&&!s1.st[p1].tr[c]) p1=s1.st[p1].fa,now=s1.st[p1].len;
    if(s1.st[p1].tr[c]){
      p1=s1.st[p1].tr[c];++now;
    }
    //printf("  p1=%d now=%d\n",p1,now);
    while(p1>1&&lenth(s1.st[p1].fa,now)>=lenth(p1,now)){
      //printf("  jump: ask=%d mx=%d rt=%d (%d %d) (%d %d)\n",
      // t.ask(t.rt[p1],1,s1.n,1,R),t.tr[t.rt[p1]].mx,t.rt[p1],1,s1.n,1,R);
      p1=s1.st[p1].fa;
    }
    now=lenth(p1,now);
    p2=s2.st[p2].tr[c];
    while(p2>1&&s2.st[s2.st[p2].fa].len>=now) p2=s2.st[p2].fa;
    ans[p2]=max(ans[p2],now);
    //printf("i=%d p1=%d p2=%d now=%d\n",i,p1,p2,now);
  }
  
  for(int i=s2.tot;i>=1;i--){
    int f=s2.st[s2.id[i]].fa;
    ans[s2.st[s2.id[i]].fa]=max(ans[f],min(s2.st[f].len,ans[s2.id[i]]));
  }
  ll res=s2.sum[1];
  //printf("tot=%lld\n",res);
  for(int i=2;i<=s2.tot;i++){
    //printf("i=%d ans=%d\n",i,ans[i]);
    res-=max(0,ans[i]-s2.st[s2.st[i].fa].len);
    ans[i]=0;
  }
  printf("%lld\n",res);
  s2.clear();
}

signed main(){
#ifndef ONLINE_JUDGE
  freopen("a.in","r",stdin);
  freopen("a.out","w",stdout);
#endif
  s1.build();
  //s1.print();
  for(int i=1;i<=s1.tot;i++) v[s1.st[i].fa].push_back(i);
  dfs(1);
  m=read();
  while(m--) work();
  return 0;
}
/*
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值