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

posted on 2020-06-26 15:09  autoint  阅读(572)  评论(0编辑  收藏  举报

导航