【洛谷6292】区间本质不同子串个数(后缀自动机+LCT)
大致题意: 给定一个字符串,求区间本质不同的子串个数。
类似题目
有一道题目和这题很像:【LOJ6041】「雅礼集训 2017 Day7」事情的相似度。
二者都是离线+\(LCT\)维护\(parent\)树+树状数组维护信息,套路基本一样。
离线
首先我们把询问按右端点排序。
这样就相当于只要不断在右边加入字符,然后维护每个左端点的答案。
后缀自动机
考虑求全局本质不同子串,显然就是一道后缀自动机智障题。
但由于限定了区间,且根据我们的转化只要维护每个左端点的答案,那么我们可以考虑记录每个子串最后一次出现的位置。
然后就会发现,每次加入第\(i\)个新字符,相当于有\(i\)个子串最后一次出现的位置都变成了\(i\),也就是\([1,i],[2,i],...,[i,i]\)。
考虑本质不同子串的计算方式,发现其实就是对于\(1\sim i\)的每个左端点答案加\(1\)。(注意最后求答案是求\([l,r]\)答案的总和,这里相当于是做了个差分)
当然,我们还要除去这些子串原先的贡献,这就略有些麻烦了。
\(LCT\)
众所周知,\(SAM\)的\(parent\)树是一棵树,而\([1,i],[2,i],...,[i,i]\)这些子串等同于前缀\(i\)的所有后缀,也就是前缀\(i\)所对应节点在\(parent\)树上到根的路径。
每次操作相当于把一个节点到根的路径最后出现的位置全部修改为\(i\),也就是染成一个之前从未出现过的颜色。
于是自然而然想到这样一道题:【BZOJ4817】[SDOI2017] 树点涂色。我们可以用\(LCT\)的每一棵\(Splay\)维护一条颜色相同的链。
每次我们\(Access\)的时候,减去这棵\(Splay\)中节点原先的贡献,这道题就做完了。
树状数组
这道题需要区间修改、区间查询,而我又懒得写线段树了,就学(抄)了一下树状数组的进阶写法。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define M 200000
#define LL long long
using namespace std;
int n,p[N+5];char s[N+5];LL ans[M+5];typedef pair<int,int> Pr;vector<Pr> q[N+5];
class SuffixAutomation//后缀自动机
{
private:
int lst;struct node {int L,F,S[30];}O[2*N+5];
public:
int Nt;I SuffixAutomation() {Nt=lst=1;}
I int operator [] (CI x) {return O[x].F;}I int operator () (CI x) {return O[x].L;}
I int Ins(CI x)//返回节点对应编号
{
RI p=lst,o=lst=++Nt;O[o].L=O[p].L+1;
W(p&&!O[p].S[x]) O[p].S[x]=o,p=O[p].F;if(!p) return O[o].F=1,o;
RI q=O[p].S[x];if(O[p].L+1==O[q].L) return O[o].F=q,o;
RI k=++Nt;(O[k]=O[q]).L=O[p].L+1,O[q].F=O[o].F=k;
W(p&&O[p].S[x]==q) O[p].S[x]=k,p=O[p].F;return o;
}
}SAM;
class TreeArray//区间修改+区间查询 树状数组
{
private:
LL a[N+5],b[N+5];I void U(RI x,CI v) {LL t=1LL*v*(x-1);W(x<=n) a[x]+=v,b[x]+=t,x+=x&-x;}
I LL Q(CI x) {RI i=x;LL t1=0,t2=0;W(i) t1+=a[i],t2+=b[i],i^=i&-i;return t1*x-t2;}
public:
I void U(CI l,CI r,CI v) {U(l,v),U(r+1,-v);}I LL Q(CI l,CI r) {return Q(r)-Q(l-1);}
}T;
class LinkCutTree//LCT
{
private:
#define PD(x) O[x].G&&(Upt(O[x].S[0],O[x].G),Upt(O[x].S[1],O[x].G),O[x].G=0)
#define Upt(x,v) (O[x].V=O[x].G=v)
#define IR(x) (O[O[x].F].S[0]^x&&O[O[x].F].S[1]^x)
#define Wh(x) (O[O[x].F].S[1]==x)
#define Co(x,y,d) (O[O[x].F=y].S[d]=x)
int St[2*N+5];struct node {int V,G,F,S[2];}O[2*N+5];
I void Ro(CI x)
{
RI f=O[x].F,p=O[f].F,d=Wh(x);!IR(f)&&(O[p].S[Wh(f)]=x),O[x].F=p,
Co(O[x].S[d^1],f,d),Co(f,x,d^1);
}
I void S(CI x)
{
RI f=x,T=0;W(St[++T]=f,!IR(f)) f=O[f].F;W(T) PD(St[T]),--T;
W(!IR(x)) f=O[x].F,!IR(f)&&(Ro(Wh(x)^Wh(f)?x:f),0),Ro(x);
}
public:
I void Init() {for(RI i=1;i<=SAM.Nt;++i) O[i].F=SAM[i];}//建树
I void Ac(RI x,CI id)//Access染色
{
RI y=0;for(RI l,r;x;x=O[y=x].F) S(x),l=SAM(O[x].F)+1,r=SAM(x),//对应的子串长度
O[x].V&&(T.U(O[x].V-r+1,O[x].V-l+1,-1),0),O[x].S[1]=y;T.U(1,id,1),Upt(y,id);//清空原贡献,最后加上新贡献
}
}LCT;
int main()
{
RI Qt,i,x,y;for(scanf("%s",s+1),n=strlen(s+1),i=1;i<=n;++i) p[i]=SAM.Ins(s[i]&31);//初始化建后缀自动机
for(scanf("%d",&Qt),i=1;i<=Qt;++i) scanf("%d%d",&x,&y),q[y].push_back(make_pair(i,x));//离线
RI j,s;for(LCT.Init(),i=1;i<=n;++i) for(LCT.Ac(p[i],i),//枚举右端点,每次更新LCT
j=0,s=q[i].size();j^s;++j) ans[q[i][j].first]=T.Q(q[i][j].second,i);//直接树状数组上询问左端点答案
for(i=1;i<=Qt;++i) printf("%lld\n",ans[i]);return 0;
}