NOI模拟 子串(substring)

本文介绍了一种结合后缀自动机与主席树的数据结构技巧,用于解决字符串问题中的区间查询与操作难题。通过使用后缀自动机来维护字符串的不同子串,以及主席树来统计重复子串的数量,实现高效求解区间内不同子串的个数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门
求支持操作:
①在原串后加一个字符。
②询问某个区间内的不同子串个数。

由不同子串个数想到后缀自动机,然而并不好维护。
一个神奇的操作:用区间总子串个数减去重复的子串个数。
(收集新套路)
如何统计重复的子串个数?
我们只保留每一个串在区间中出现的最后一个位置就行了,也就是 e n d p o s endpos endpos最大的一个位置。对于其他的 e n d p o s endpos endpos,在开头的位置打一个减一标记。询问区间 [ l , r ] [l,r] [l,r]的时候统计区间和即可。

如何打标记?
而当新加入一个字符,它到根节点的路径上( p a r e n t parent parent树)的节点的 e n d p o s endpos endpos会新增一个当前字符的位置,成为这些节点的保留位置。而在更新这个保留位置之前,他们有一个之前的保留位置。把这些之前的保留位置对应的开头在主席树上打上标记,然后再把它们的保留位置更新为当前位置即可。

有如下几个坑点(手残+脑残):

由于在 a c c e s s access access的时候, q q q f a [ q ] fa[q] fa[q]之间的实边会断掉,那么先 a c c e s s access access当前节点,再把 q q q的父亲指针改掉就好了,就不用写 c u t cut cut

然后要注意的一点是根节点的长度为0,空节点的长度为0,当 a c c e s s access access到根节点的时候会导致左区间大于右区间。所以要留心主席树的询问操作写法。

S A M SAM SAM中的克隆节点的部分,当修改 D A G DAG DAG中指向 q q q的边时, p p p是已经变了的,所以要在这之前把 l e n [ c l o n e ] = l e n [ p ] + 1 len[clone]=len[p]+1 len[clone]=len[p]+1写了。。

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const
namespace IO{
	cs int Rlen=1<<22|1;
	char buf[Rlen],*p1,*p2;
	inline char gc(){return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;}
	inline char peek(){return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1;}
	inline char ga(){while(!isalpha(peek()))++p1;return *p1++;}
	template<typename T>
	inline T get(){
		char c;T num;
		while(!isdigit(c=gc()));num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
	inline int gi(){return get<int>();}
	inline ll gl(){return get<ll>();}
}
using namespace IO;
cs int N=1e5+7;
int M;
namespace PST{
	cs int N=::N*80;
	int lc[N],rc[N],tag[N],tot;ll sum[N];
	inline int cpy(int u){++tot;lc[tot]=lc[u],rc[tot]=rc[u],tag[tot]=tag[u],sum[tot]=sum[u];return tot;}
	inline void update(int &u,int l,int r,int ql,int qr,int val){
		u=cpy(u),sum[u]+=1ll*(std::min(r,qr)-std::max(l,ql)+1)*val;
		if(ql<=l&&r<=qr){tag[u]+=val;return;}
		int mid=l+r>>1;
		if(ql<=mid) update(lc[u],l,mid,ql,qr,val);
		if(qr> mid) update(rc[u],mid+1,r,ql,qr,val);
	}
	inline ll query(int u,int l,int r,int ql,int qr,ll ret=0){
		if(!u) return 0;if(ql<=l&&r<=qr) return sum[u];
		int mid=l+r>>1;ll laz=1ll*(std::min(r,qr)-std::max(l,ql)+1)*tag[u];
		if(qr<=mid) return laz+query(lc[u],l,mid,ql,qr);
		if(ql> mid) return laz+query(rc[u],mid+1,r,ql,qr);
		return laz+query(lc[u],l,mid,ql,mid)+query(rc[u],mid+1,r,mid+1,qr);
	}
}
int rt[N],len[N<<1];
namespace LCT{
	cs int N=::N<<1;
	int fa[N],son[N][2],tag[N],last[N];
	inline void pushnow(int x,int v){tag[x]=last[x]=v;}
	inline int isroot(int x){return (son[fa[x]][0]!=x)&&(son[fa[x]][1]!=x);}
	inline int get(int x){return x==son[fa[x]][1];}
	inline void pushdown(int x){if(tag[x]) pushnow(son[x][0],tag[x]),pushnow(son[x][1],tag[x]),tag[x]=0;}
	inline void rotate(int x){
		int y=fa[x],z=fa[y],k=get(x),l=son[x][k^1];
		fa[x]=z;if(!isroot(y)) son[z][get(y)]=x;
		fa[y]=x,son[x][k^1]=y,fa[l]=y,son[y][k]=l;
	}
	inline void splay(int x){
		static int st[N],top;st[top=1]=x;
		for(int re i=x;!isroot(i);i=fa[i]) st[++top]=fa[i];
		while(top) pushdown(st[top--]);
		for(int re y=fa[x];!isroot(x);rotate(x),y=fa[x])
			if(!isroot(y)) rotate(get(y)==get(x)?y:x);
	}
	inline void access(int x,int id){
		int y=0;for(;x;x=fa[y=x]){
			splay(x);if(last[x])
			PST::update(rt[id],1,M,last[x]-len[x]+1,last[x]-len[fa[x]],1);
			son[x][1]=y;
		}pushnow(y,id);
	}
	inline int get_last(int x){splay(x);return last[x];}
	inline void link(int u,int f){splay(u),fa[u]=f;}
}
using LCT::link;
using LCT::access;
int len_all;
namespace SAM{
	cs int N=::N<<1|1;
	int son[N][26],fa[N],sz=1,last=1;
	inline void push_back(int c){
		int id=++len_all,cur=++sz,p=last;rt[id]=rt[id-1],last=cur;
		len[cur]=len[p]+1;
		for(;p&&!son[p][c];p=fa[p])son[p][c]=cur;
		if(!p) link(cur,fa[cur]=1),access(cur,id);
		else{
			int q=son[p][c];
			if(len[q]==len[p]+1) link(cur,fa[cur]=q),access(cur,id);
			else{
				int clone=++sz;
				memcpy(son[clone],son[q],sizeof son[q]),len[clone]=len[p]+1;
				for(;p&&son[p][c]==q;p=fa[p])son[p][c]=clone;
				LCT::last[clone]=LCT::get_last(q);
				link(clone,fa[clone]=fa[q]),link(cur,fa[cur]=clone);
				access(cur,id),link(q,fa[q]=clone);
			}
		}
	}
}
ll ans;
int n,m,op,kind,l,r;
char s[N];
signed main(){
	scanf("%d%s%d",&op,s+1,&m),M=(n=strlen(s+1))+m;
	for(int re i=1;i<=n;++i)SAM::push_back(s[i]-'a');
	while(m--){
		kind=gi();
		if(kind==1) SAM::push_back((ans*op+gc()-'a')%26);
		if(kind==2){
			l=(ans*op+gi()-1)%len_all+1,r=(ans*op+gi()-1)%len_all+1;
			printf("%lld\n",ans=(1ll*(r-l+2)*(r-l+1)/2-PST::query(rt[r],1,M,l,r)));
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值