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;
}