把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷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;
}
posted @ 2020-07-21 12:10  TheLostWeak  阅读(427)  评论(0编辑  收藏  举报