首页 > 其他分享 >区间本质不同子串个数

区间本质不同子串个数

时间:2024-01-19 12:13:09浏览次数:35  
标签:子串 par val idx int text 个数 本质 len

传送门

LCT 好题。

description

给定一个长度为 \(n\) 的只包含小写英文字母的字符串。

有 \(m\) 次询问,每次问一个子串的本质不同子串个数。

  • \(n\leq 10^5\)

  • \(m\leq 2\times 10^5\)

solution

考虑按询问右端点从小到大离线。

先建出母串的 SAM,设 \(\text{len}_x\) 表示状态 \(x\) 代表的等价类中最长的子串的长度,\(\text{rpos}_x\) 表示 \(\max\limits_{r_i\in \text{endpos}(x)}\{r_i\}\)。

我们让所有子串只在它最后一次出现的位置贡献,那么可以这样算母串的本质不同子串个数:

  1. 对于每个 SAM 状态,对 \([\text{rpos}_x-\text{len}_x+1,\text{rpos}_x-\text{len}_{fa_x}]\) 做区间加。

  2. 统计 \(i\in [1,n]\) 每个位置的和。

可以发现,这样做能让 \([l,n]\) 的和表示左端点在 \(l\) 及其右边的本质不同子串的数量。

所以我们只需要在右指针右移过程中维护每个位置的和就行了。

观察到 \(\text{len}\) 不会随着右指针的右移发生改变,所以我们只需要考虑 \(\text{rpos}\) 的变化。

考虑母串的 parent 树,右指针从 \(i-1\) 右移到 \(i\) 就相当于从前缀 \([1,i]\) 所在的状态开始往上跳,把它和它所有祖先的 \(\text{rpos}\) 改成 \(i\)(因为 \(i\) 一定是当前所有 \(\text{endpos}\) 集合中最大的元素)。

因为这条链上的每个点的 \(\text{rpos}\) 并非完全一样,所以不好直接维护。

但是注意到这是个类似染色的过程,能让我们联想到 LCT 的 access 操作,每条实链上节点的 \(\text{rpos}\) 都相同。

于是直接用 LCT 维护每条实链的 \(\text{rpos}\),在 access 的过程中进行区间加减操作即可。维护区间加区间和可以用线段树实现。

询问的话只需要在线段树上询问 \([l,r]\) 的区间和。

时间复杂度 \(O(n\log^2 n+m\log n)\)。

code

#include<bits/stdc++.h>

using namespace std;
using E=long long;


struct segment{
  vector<E> sum,tag,len;

  void build(int u,int l,int r){
    len[u]=r-l+1;
    if(l==r) return ;
    int mid=(l+r)>>1;
    build(u<<1,l,mid),build(u<<1|1,mid+1,r);
  }

  segment(int sz=0){
    sum=tag=len=vector<E>(sz*4+10);
    if(!sz) return ;
    build(1,1,sz);
  }

  void spread(int u,int val){
    if(!u) return ;
    sum[u]+=val*1ll*len[u];
    tag[u]+=val;
  }

  void dn(int u){
    if(!tag[u]) return ;
    spread(u<<1,tag[u]),spread(u<<1|1,tag[u]);
    tag[u]=0;
  }

  void add(int u,int l,int r,int L,int R,int val){
    if(L<=l&&r<=R){
      spread(u,val);
      return ;
    }
    dn(u);
    int mid=(l+r)>>1;
    if(L<=mid) add(u<<1,l,mid,L,R,val);
    if(mid<R) add(u<<1|1,mid+1,r,L,R,val);
    sum[u]=sum[u<<1]+sum[u<<1|1];
  }

  E query(int u,int l,int r,int L,int R){
    if(L<=l&&r<=R) return sum[u];
    dn(u);
    E ret=0;
    int mid=(l+r)>>1;
    if(L<=mid) ret=query(u<<1,l,mid,L,R);
    if(mid<R) ret+=query(u<<1|1,mid+1,r,L,R);
    return ret;
  }

};

struct linkCutTree{
  struct node{
    int son[2],par;
    int val,tag,w;
  };

  vector<node> tr;
  segment T;
  int n;
  linkCutTree(int sz=0){
    tr=vector<node>(sz+1);
    T=segment(sz);
    n=sz;
  }

  #define ls(u) tr[(u)].son[0]
  #define rs(u) tr[(u)].son[1]
  #define par(u) tr[(u)].par
  #define val(u) tr[(u)].val
  #define w(u) tr[(u)].w
  #define tag(u) tr[(u)].tag

  void spread(int u,int val){ if(!u) return ; w(u)=val,tag(u)=val; }
  void dn(int u){ if(!tag(u)) return ; spread(ls(u),tag(u)),spread(rs(u),tag(u)),tag(u)=0; }
  int nroot(int u){ return u==ls(par(u))||u==rs(par(u)); }
  int get(int u){ return u==rs(par(u)); }

  void rotate(int u){
    int p=par(u),j=par(p),op=get(u);
    if(nroot(p)) tr[j].son[get(p)]=u;
    if(tr[u].son[op^1]) par(tr[u].son[op^1])=p;
    tr[p].son[op]=tr[u].son[op^1];
    tr[u].son[op^1]=p;
    par(p)=u;
    par(u)=j;
  }

  void update(int u){
    if(nroot(u)) update(par(u));
    dn(u);
  }

  void splay(int u){
    update(u);
    while(nroot(u)){
      int f=par(u);
      if(nroot(f)){
        if(get(u)==get(f)) rotate(f);
        else rotate(u);
      }
      rotate(u);
    }
  }

  int top(int u){
    splay(u);
    dn(u);
    while(ls(u)) u=ls(u),dn(u);
    return splay(u),u;
  }

  void access(int u,int now){
    int p=0;
    while(u){
      int t=top(u);
      if(w(u)&&val(u)) T.add(1,1,n,w(u)-val(u)+1,w(u)-val(par(t)),-1);
      if(val(u)) T.add(1,1,n,now-val(u)+1,now-val(par(t)),1);
      splay(u);
      w(u)=now;
      if(ls(u)) spread(ls(u),now);
      rs(u)=p;
      p=u;
      u=par(u);
    }
  }

  void reset(int u,int p,int v){
    val(u)=v,par(u)=p;
    w(u)=0;
  }

  E query(int l,int r){
    assert(l<=r);
    return T.query(1,1,n,l,r);
  }

  #undef ls
  #undef rs
  #undef par
  #undef val
  #undef w
  #undef tag

};

struct SAM{
  vector<array<int,26>> ch;
  vector<int> fa,len;
  int idx,lst;
  linkCutTree T;

  SAM(int sz){
    idx=1;
    lst=1;
    ch=vector<array<int,26>>(sz*2);
    fa=len=vector<int>(sz*2);
  }

  inline int f(char c){ return (int)(c-'a'); }

  int extend(char c){
    idx++;
    int p=lst;
    len[idx]=len[lst]+1;
    while(p&&ch[p][f(c)]==0){
      ch[p][f(c)]=idx;
      p=fa[p];
    }
    if(!p){
      fa[idx]=1;
      return lst=idx,idx;
    }
    int q=ch[p][f(c)];
    if(len[q]==len[p]+1){
      fa[idx]=q;
      return lst=idx,idx;
    }
    int np=idx;
    idx++;
    ch[idx]=ch[q]; len[idx]=len[p]+1; fa[idx]=fa[q];
    while(p&&ch[p][f(c)]==q){
      ch[p][f(c)]=idx;
      p=fa[p];
    }
    fa[q]=idx,fa[np]=idx;
    lst=np;
    return np;
  }

  void build(){
    T=linkCutTree(idx);
    for(int i=1; i<=idx; i++){
      //cerr<<i<<' '<<fa[i]<<' '<<len[i]<<endl;
      T.reset(i,fa[i],len[i]);
    }
  }
};

int main(){

#ifdef zzafanti
  freopen("in.in","r",stdin);
#endif // zzafanti

  cin.tie(nullptr),cout.tie(nullptr)->sync_with_stdio(false);

  string str;
  int n,m;
  cin>>str;
  n=str.size();
  str=" "+str;

  cin>>m;

  vector<vector<pair<int,int>>> Q(n+1);
  for(int i=1; i<=m; i++){
    int l,r;
    cin>>l>>r;
    Q[r].emplace_back(make_pair(i,l));
  }

  SAM sam(n);

  vector<int> pos(n+1);
  for(int i=1; i<=n; i++){
    pos[i]=sam.extend(str[i]);
  }
  sam.build();

  vector<E> ans(m+1);
  for(int i=1; i<=n; i++){
    sam.T.access(pos[i],i);
    for(auto pt:Q[i]){
      int idx=pt.first,l=pt.second;
      ans[idx]=sam.T.query(l,i);
    }
  }

  for(int i=1; i<=m; i++){
    cout<<ans[i]<<'\n';
  }

  return 0;
}

标签:子串,par,val,idx,int,text,个数,本质,len
From: https://www.cnblogs.com/FreshOrange/p/17974342

相关文章

  • 论文翻译 | 【深入挖掘Java技术】「底层原理专题」深入分析一下并发编程之父Doug Lea
    前提介绍DougLea在州立大学奥斯威戈分校(DougLea)摘要本文深入探讨了一个Java框架的设计、实现及其性能。该框架遵循并行编程的理念,通过递归方式将问题分解为多个子任务,并利用工作窃取技术进行并行处理。所有子任务完成后,其结果被整合以形成完整的并行程序。在总体设计上,该框架借鉴......
  • dotnet 连接多个数据库
    SqliteToOracle\global.json{"sdk":{"version":"7.0.401"}}SqliteToOracle\SqliteToOracle.slnMicrosoftVisualStudioSolutionFile,FormatVersion12.00#VisualStudioVersion17VisualStudioVersion=17.0.3......
  • PHP把数组按指定的个数分隔
    假设数组为array(‘1’,‘2’,‘3’,‘4’,‘5’,‘6’);想把它分割成四个,那么结果为array( ‘0’=>[‘1’,‘2’], ‘1’=>[‘3’,‘4’], ‘2’=>[‘5’], ‘3’=>[‘6’],);/****把数组按指定的个数分隔*@paramarray$array要分割的数组*@par......
  • 2024-01-17:lc的30. 串联所有单词的子串
    2024-01-17:用go语言,给定一个字符串s和一个字符串数组words。words中所有字符串长度相同。s中的串联子串是指一个包含words中所有字符串以任意顺序排列连接起来的子串。例如,如果words=["ab","cd","ef"],那么"abcdef","abefcd","cdabef","cdefab",&quo......
  • E - Ring MST(n个数裴蜀定理)
    E-RingMST有i种操作,第i种操作为选择一个数x,然后在x和(x+a[i])%N之间连边,代价为c[i],问是否能够让图联通,如果可以最小生成树的边权和是多少。首先按照克鲁斯卡尔算法,我们肯定是按照边权从小到大连。考虑前i种操作都操作完后的连通块个数。若u,v在同一联通块,则\(u\equivv+a......
  • 22. 从零用Rust编写正反向代理,一个数据包的神奇HTTP历险记!
    wmproxywmproxy已用Rust实现http/https代理,socks5代理,反向代理,静态文件服务器,四层TCP/UDP转发,内网穿透,后续将实现websocket代理等,会将实现过程分享出来,感兴趣的可以一起造个轮子项目地址国内:https://gitee.com/tickbh/wmproxygithub:https://github.com/tickbh/wmproxy数......
  • Java Collections.frequency()方法返回集合中指定元素个数
    JavaCollections.frequency()方法具有什么功能呢?下文笔者讲述Collections.frequency()方法的功能简介说明,如下所示:Collections.frequency()方法的功能:返回一个int值,其值给指定对象在集合中出现的次数Collections.frequency()方法的语法publicstaticintfreque......
  • 将一个数组中的元素头尾两端对调
    #include<stdio.h>voidinplace_swap(int*x,int*y){printf("--------------\n");printf("x=%d,y=%d\n",*x,*y);*y=*x^*y;printf("x=%d,y=%d\n",*x,*y);*x=*x^*y;printf(&......
  • 无重复字符的最长子串
    给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。示例 1:输入:s="abcabcbb"输出:3解释:因为无重复字符的最长子串是"abc",所以其长度为3。示例2:输入:s="bbbbb"输出:1解释:因为无重复字符的最长子串是"b",所以其长度为1。示例3:......
  • 无重复字符的最长子串2
    classSolution{public:intlengthOfLongestSubstring(strings){//哈希集合,记录每个字符是否出现过unordered_set<char>occ;intn=s.size();//右指针,初始值为-1,相当于我们在字符串的左边界的左侧,还没有开始移动int......