LG6292 区间本质不同子串个数
区间本质不同子串个数
给定一个长度为 \(n\) 的字符串 \(S\),\(m\) 次询问由 \(S\) 的第 \(L\) 到第 \(R\) 个字符组成的字符串包含多少个本质不同的子串。
定义两个字符串 \(a,b\) 相同当且仅当 \(|a|=|b|\) 并且对于 \(i\in[1,|a|]\) 都有 \(a_i=b_i\)。
对于 \(100\%\) 的数据,满足 \(1\leq n\leq 10^5\),\(1\leq m\leq 2\times 10^5\),\(1\leq l_i\leq r_i\leq n(i\in[1,m])\)。
题解
https://www.luogu.com.cn/blog/fuyuki/solution-p6292
对于每个本质不同的字符串 \(T\) ,假设在前缀 \([1,r]\) 中最后一次出现的位置(右端点)为 \(\text{last}_T\) ,那么当左端点取到 \([1,\text{last}_T-|T|+1]\) 这个区间内的时候, \(T\) 会对答案产生 \(1\) 的贡献。
离线下来,对每个右端点维护所有左端点的答案。
如果把反串的后缀树建出来,那么将右端点右移一位到达 \(r\) 就相当于将后缀树上一条到根的路径上的所有字符串的 \(\text{last}\) 修改成 \(r\) 。如果把这个看作 LCT 中的 access 操作,可以发现划分出来的每条链上的 \(\text{last}\) 都相同,并且代表的字符串长度连续。
因此直接用 LCT 进行修改,将链合并的时候就是将一段长度连续的本质不同字符串的 \(\text{last}\) 进行修改,这个对答案的影响可以表现成区间加一个公差为 \(1\) 的等差数列,用线段树维护即可。
注意到 access 操作的次数是均摊 \(O(\log n)\) ,那么只会进行 \(O(n\log n)\) 次线段树上的区间修改,复杂度是 \(O(n\log^2 n)\) 的。而线段树上查询一次的复杂度是 \(O(\log n)\) ,所以总复杂度是 \(O(n\log^2 n+m\log n)\) 。
实现中将区间加等差数列单点查询转化成区间加常数区间查询。
CO int N=2e5+10,inf=1e9; namespace SAM{ int last=1,tot=1; int ch[N][26],fa[N],len[N],idx[N]; void extend(int c,int p){ int x=last,cur=last=++tot; len[cur]=len[x]+1,idx[p]=cur; for(;x and !ch[x][c];x=fa[x]) ch[x][c]=cur; if(!x) {fa[cur]=1; return;} int y=ch[x][c]; if(len[y]==len[x]+1) {fa[cur]=y; return;} int clone=++tot; copy(ch[y],ch[y]+26,ch[clone]); fa[clone]=fa[y],len[clone]=len[x]+1; fa[cur]=fa[y]=clone; for(;ch[x][c]==y;x=fa[x]) ch[x][c]=clone; } } namespace SEG{ int64 sum[4*N],tag[4*N]; #define lc (x<<1) #define rc (x<<1|1) #define mid ((l+r)>>1) IN void push_up(int x){ sum[x]=sum[lc]+sum[rc]; } IN void put_tag(int x,int l,int r,int64 v){ sum[x]+=v*(r-l+1),tag[x]+=v; } void push_down(int x,int l,int r){ if(tag[x]){ put_tag(lc,l,mid,tag[x]),put_tag(rc,mid+1,r,tag[x]); tag[x]=0; } } void modify(int x,int l,int r,int ql,int qr,int64 v){ if(ql<=l and r<=qr) return put_tag(x,l,r,v); push_down(x,l,r); if(ql<=mid) modify(lc,l,mid,ql,qr,v); if(qr>mid) modify(rc,mid+1,r,ql,qr,v); push_up(x); } int64 query(int x,int l,int r,int ql,int qr){ if(ql<=l and r<=qr) return sum[x]; push_down(x,l,r); if(qr<=mid) return query(lc,l,mid,ql,qr); if(ql>mid) return query(rc,mid+1,r,ql,qr); return query(lc,l,mid,ql,qr)+query(rc,mid+1,r,ql,qr); } #undef lc #undef rc #undef mid } namespace LCT{ int n,ch[N][2],fa[N]; int pos[N],tag[N],len[N],low[N]; IN bool nroot(int x){ return ch[fa[x]][0]==x or ch[fa[x]][1]==x; } IN void push_up(int x){ low[x]=min(len[x],min(low[ch[x][0]],low[ch[x][1]])); } IN void put_tag(int x,int p){ pos[x]=tag[x]=p; } void push_down(int x){ if(tag[x]){ put_tag(ch[x][0],tag[x]),put_tag(ch[x][1],tag[x]); tag[x]=0; } } void rotate(int x){ int y=fa[x],z=fa[y],l=x==ch[y][1],r=l^1; if(nroot(y)) ch[z][y==ch[z][1]]=x;fa[x]=z; ch[y][l]=ch[x][r],fa[ch[x][r]]=y; ch[x][r]=y,fa[y]=x; push_up(y),push_up(x); } void push_all(int x){ if(nroot(x)) push_all(fa[x]); push_down(x); } void splay(int x){ push_all(x); for(;nroot(x);rotate(x)){ int y=fa[x],z=fa[y]; if(nroot(y)) rotate((x==ch[y][1])!=(y==ch[z][1])?x:y); } } void access(int x,int p){ for(int i=x,y=0;i;y=i,i=fa[i]){ // edit 1: x will be used later splay(i); if(pos[i]) SEG::modify(1,1,n,pos[i]-SAM::len[i]+1,pos[i]-low[i]+1,-1); ch[i][1]=y; push_up(i); } splay(x); put_tag(x,p); SEG::modify(1,1,n,p-SAM::len[x]+1,p,1); } } char str[N]; vector<pair<int,int> > qry[N]; int64 ans[N]; int main(){ scanf("%s",str+1); int n=strlen(str+1); for(int i=1;i<=n;++i) SAM::extend(str[i]-'a',i); int m=read<int>(); for(int i=1;i<=m;++i){ int l=read<int>(),r=read<int>(); qry[r].push_back(make_pair(l,i)); } LCT::n=n,LCT::low[0]=inf; for(int i=1;i<=SAM::tot;++i){ LCT::fa[i]=SAM::fa[i]; LCT::len[i]=LCT::low[i]=SAM::len[SAM::fa[i]]+1; } for(int i=1;i<=n;++i){ LCT::access(SAM::idx[i],i); for(int j=0;j<(int)qry[i].size();++j) ans[qry[i][j].second]=SEG::query(1,1,n,qry[i][j].first,i); } for(int i=1;i<=m;++i) printf("%lld\n",ans[i]); return 0; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步