区间本质不同子串个数
一、题目
二、解法
这道题需要离线,我们一个一个加入字符,然后回答右端点相应的询问。那对于不同的左端点怎么办呢?可以暴力一点:尝试 维护出所有左端点的答案,然后直接暴力拿就行了。
首先对于一个本质不同的子串,设他的最后出现位置是 \(pos\)(指的是 \(\tt endpos\) ),那么对于左端点 \(l\in[1,pos-|T|+1]\) 就可以获得 \(1\) 的贡献。而根据后缀自动机的性质,每次只会是 \(last\) 到根的链上会有修改,而他们的 \(pos\) 都会变成现在的 \(i\)
要搞出一个能维护链的数据结构,你仔细想一下就发现树链剖分也没有什么用。我们选择 \(\tt link-cut-tree\)
但要对它有一个更深的理解,因为我们的修改本质上是 后缀树上到根链的修改套上线段树上的修改 (因为要维护所有左端点的答案,实现中线段树维护的是差分标记),我们唯一可以加速的就是把 \(pos\) 相同且紧挨在一起的点一起修改了(因为子串长度是连续的)
又要保证复杂度,所以我们把他和 \(\tt link-cut-tree\) 上 \(\tt splay\) 的均摊分析联系在一起,我们在 \(access\) 操作的时候,把 \(x\) 转到根以后,直接把他管辖的这一小段一并改了(具体写法就是改长度 \((len[par[x]],len[x]]\) 的 \(pos\)),因为 这一小段原来是实边相连的,他们的 \(pos\) 一定是一样的 。
\(access\) 的基本复杂度是 \(O(n\log n)\),套上他之后就变成了 \(O(n\log^2 n)\),写起来并不麻烦。
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 200005;
#define ll long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
//suffix tree
int n,m,cnt,last,p[M];char s[M];
vector<pair<int,int> > q[M];ll ans[M];
struct node
{
int fa,len,ch[26];
}a[M];
void add(int c)
{
int p=last,np=last=++cnt;
a[np].len=a[p].len+1;
for(;p && !a[p].ch[c];p=a[p].fa) a[p].ch[c]=np;
if(!p) a[np].fa=1;
else
{
int q=a[p].ch[c];
if(a[q].len==a[p].len+1) a[np].fa=q;
else
{
int nq=++cnt;
a[nq]=a[q];a[nq].len=a[p].len+1;
a[q].fa=a[np].fa=nq;
for(;p && a[p].ch[c]==q;p=a[p].fa) a[p].ch[c]=nq;
}
}
}
//segment tree
struct jzm
{
ll sum[2*M],tag[2*M];
void up(int i)
{
sum[i]=sum[i<<1]+sum[i<<1|1];
}
void down(int i,int l,int r)
{
int mid=(l+r)>>1;
if(tag[i])
{
tag[i<<1]+=tag[i];
tag[i<<1|1]+=tag[i];
sum[i<<1]+=1ll*tag[i]*(mid-l+1);
sum[i<<1|1]+=1ll*tag[i]*(r-mid);
tag[i]=0;
}
}
void upd(int i,int l,int r,int L,int R,int x)
{
if(L>r || l>R) return ;
if(L<=l && r<=R)
{
sum[i]+=1ll*(r-l+1)*x;
tag[i]+=x;
return ;
}
int mid=(l+r)>>1;
down(i,l,r);
upd(i<<1,l,mid,L,R,x);
upd(i<<1|1,mid+1,r,L,R,x);
up(i);
}
ll ask(int i,int l,int r,int L,int R)
{
if(l>R || L>r) return 0;
if(L<=l && r<=R) return sum[i];
int mid=(l+r)>>1;
down(i,l,r);
return ask(i<<1,l,mid,L,R)+ask(i<<1|1,mid+1,r,L,R);
}
}T;
//link-cut-tree
int par[M],ch[M][2],val[M],tag[M],st[M];
int nrt(int x)
{
return ch[par[x]][0]==x || ch[par[x]][1]==x;
}
int chk(int x)
{
return ch[par[x]][1]==x;
}
void down(int x)
{
if(x && tag[x])
{
if(ch[x][0]) val[ch[x][0]]=tag[ch[x][0]]=tag[x];
if(ch[x][1]) val[ch[x][1]]=tag[ch[x][1]]=tag[x];
tag[x]=0;
}
}
void rotate(int x)
{
int y=par[x],z=par[y],k=chk(x),w=ch[x][k^1];
ch[y][k]=w;par[w]=y;
if(nrt(y)) ch[z][chk(y)]=x;par[x]=z;
ch[x][k^1]=y;par[y]=x;
}
void splay(int x)
{
int y=x,z=0;
st[++z]=y;
while(nrt(y)) st[++z]=y=par[y];
while(z) down(st[z--]);
while(nrt(x))
{
int y=par[x],z=par[y];
if(nrt(y))
{
if(chk(y)==chk(x)) rotate(y);
else rotate(x);
}
rotate(x);
}
}
void access(int x,int k)
{
int y=0;
for(;x;x=par[y=x])
{
splay(x);ch[x][1]=y;
if(val[x]) T.upd(1,1,n,val[x]-a[x].len+1,val[x]-a[par[x]].len,-1);
}
val[y]=tag[y]=k;T.upd(1,1,n,1,k,1);
}
signed main()
{
scanf("%s",s+1),n=strlen(s+1);m=read();
last=cnt=1;
for(int i=1;i<=n;i++)
{
add(s[i]-'a');
p[i]=last;
}
for(int i=2;i<=cnt;i++) par[i]=a[i].fa;
for(int i=1;i<=m;i++)
{
int l=read(),r=read();
q[r].push_back(make_pair(l,i));
}
for(int i=1;i<=n;i++)
{
access(p[i],i);
for(int j=0;j<q[i].size();j++)//nmsl,这里打成i++了...
{
int l=q[i][j].first,id=q[i][j].second;
ans[id]=T.ask(1,1,n,l,i);
}
}
for(int i=1;i<=m;i++)
printf("%lld\n",ans[i]);
}