【NOI2018】你的名字(后缀自动机,线段树合并)

该博客讨论了一种字符串处理问题,即如何在给定字符串SSS和多个区间[l,r][l,r][l,r]下,快速计算与另一系列字符串TTT的本质不同公共子串数目。通过构建后缀自动机和线段树,实现了O(nlogn)O(nlog n)O(nlogn)的时间复杂度解决方案。文章深入解析了算法思路,并提供了完整的C++实现代码。

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

题意:

给定一个字符串 S S S q q q 次询问 T , l , r T,l,r T,l,r,求 T T T S [ l , r ] S[l,r] S[l,r] 的本质不同公共子串数目。

∣ S ∣ ≤ 5 × 1 0 5 |S|\leq 5\times 10^5 S5×105 q ≤ 1 0 5 q\leq 10^5 q105 ∑ ∣ T ∣ ≤ 1 0 6 \sum |T|\leq 10^6 T106

题解:

首先看一个弱化版的问题:给出两个串 S , T S,T S,T,求 S , T S,T S,T 的本质不同公共子串数目。

由于要求本质不同,所以思路是建出 T T T 的后缀自动机,然后看每一个节点所对应的那一些串中有多少是 S S S 的子串,即这个节点中长度小于等于多少的串都是 S S S 的子串。

为了求出这个,我们需要求出 T T T 的每一个前缀的最长后缀使得它是 S S S 的子串,也就是 T T T S S S 的后缀自动机上匹配的过程:每次加入一个字符 c c c,不断跳 f a fa fa 直到当前节点存在 c c c 的出边为止。

时间复杂度 O ( ∣ S ∣ + ∣ T ∣ ) O(|S|+|T|) O(S+T),此处看作后缀自动机的构造为线性。

但现在询问的是 S [ l , r ] S[l,r] S[l,r] T T T 的本质不同公共子串数目。我们考虑仍然使用这个思路,不过要做些许修改。

要做修改的地方是 T T T S S S 后缀自动机上匹配的过程,我们求出了 T T T 当前前缀的最长后缀使得它是 S S S 的子串后,可能这个后缀并不是 S [ l , r ] S[l,r] S[l,r] 的子串。那么应该继续往上跳,直到跳到一个点 u u u 使得 u u u 代表的所有串中有一个串出现在 S [ l , r ] S[l,r] S[l,r] 内了。也就是说存在一个 p ∈ endpos ⁡ ( u ) p\in\operatorname{endpos}(u) pendpos(u) 使得 l ≤ p − l e n ( f a ( p ) ) ∧ p ≤ r l\leq p-len(fa(p))\land p\leq r lplen(fa(p))pr。相当于说我们要找到 endpos ⁡ ( u ) \operatorname{endpos}(u) endpos(u) 里面小于等于 r r r 的最大的元素并 check 是否满足上面的条件。

那么我们就需要维护一个节点的 endpos ⁡ \operatorname{endpos} endpos 集合,使用线段树合并即可,需要可持久化。时空复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

#include<bits/stdc++.h>

#define N 500010
#define ll long long

using namespace std;

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

int n,m,q,L,R,lsp[N];
char s[N],t[N];

namespace SAMS
{
	namespace Seg
	{
		#define lc(u) ch[u][0]
		#define rc(u) ch[u][1]
		const int NN=20000000;
		int node,ch[NN][2],size[NN];
		int copy(int u)
		{
			++node,lc(node)=lc(u),rc(node)=rc(u),size[node]=size[u];
			return node;
		}
		void up(int u){size[u]=size[lc(u)]+size[rc(u)];}
		void update(int &u,int l,int r,int x)
		{
			if(!u) u=++node;
			if(l==r)
			{
				size[u]++;
				return;
			}
			int mid=(l+r)>>1;
			if(x<=mid) update(lc(u),l,mid,x);
			else update(rc(u),mid+1,r,x);
			up(u);
		}
		void merge(int &a,int b,int l,int r)
		{
			if(!a||!b)
			{
				a=a+b;
				return;
			}
			a=copy(a);
			int mid=(l+r)>>1;
			merge(lc(a),lc(b),l,mid);
			merge(rc(a),rc(b),mid+1,r);
			up(a);
		}
		int find(int u,int l,int r,int x)
		{
			if(!size[u]) return -1;
			if(l==r) return l;
			int mid=(l+r)>>1;
			if(x>mid)
			{
				int tmp=find(rc(u),mid+1,r,x);
				if(tmp!=-1) return tmp;
			}
			return find(lc(u),l,mid,x);
		}
		#undef lc
		#undef rc
	}
	const int NN=N<<1;
	int last=1,node=1,ch[NN][26],len[NN],fa[NN],rt[NN];
	void insert(int c,int id)
	{
		int p=last,now=last=++node;
		len[now]=len[p]+1,Seg::update(rt[now],1,n,id);
		for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=now;
		if(!p) fa[now]=1;
		else
		{
			int q=ch[p][c];
			if(len[p]+1==len[q]) fa[now]=q;
			else
			{
				int nq=++node;
				memcpy(ch[nq],ch[q],sizeof(ch[nq]));
				len[nq]=len[p]+1;
				fa[nq]=fa[q],fa[q]=fa[now]=nq;
				for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
			}
		}
	}
	void init()
	{
		for(int i=1;i<=n;i++) insert(s[i]-'a',i);
		static int id[NN];
		for(int i=1;i<=node;i++) id[i]=i;
		sort(id+1,id+node+1,[&](int a,int b){return len[a]<len[b];});
		for(int i=node;i>=2;i--) Seg::merge(rt[fa[id[i]]],rt[id[i]],1,n);
	}
	void work()
	{
		int u=1,nlen=0;
		for(int i=1;i<=m;i++)
		{
			int c=t[i]-'a';
			while(u!=1&&!ch[u][c]) u=fa[u],nlen=len[u];
			if(ch[u][c]) u=ch[u][c],nlen++;
			while(1)
			{
				int p=Seg::find(rt[u],1,n,R);
				if(p!=-1&&p-len[fa[u]]>=L)
				{
					nlen=min(nlen,p-L+1);
					break;
				}
				u=fa[u],nlen=len[u];
			}
			lsp[i]=nlen;
		}
	}
}

namespace SAMT
{
	const int NN=N<<1;
	int last=1,node=1,ch[NN][26],len[NN],fa[NN],endpos[NN];
	void clear()
	{
		for(int i=1;i<=node;i++)
		{
			memset(ch[i],0,sizeof(ch[i]));
			len[i]=fa[i]=endpos[i]=0;
		}
		last=node=1;
	}
	void insert(int c,int id)
	{
		int p=last,now=last=++node;
		len[now]=len[p]+1,endpos[now]=id;
		for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=now;
		if(!p) fa[now]=1;
		else
		{
			int q=ch[p][c];
			if(len[p]+1==len[q]) fa[now]=q;
			else
			{
				int nq=++node;
				memcpy(ch[nq],ch[q],sizeof(ch[nq]));
				len[nq]=len[p]+1;
				fa[nq]=fa[q],fa[q]=fa[now]=nq;
				for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
			}
		}
	}
	ll work()
	{
		clear();
		for(int i=1;i<=m;i++)
			insert(t[i]-'a',i);
		static int id[NN];
		for(int i=1;i<=node;i++) id[i]=i;
		sort(id+1,id+node+1,[&](int a,int b){return len[a]<len[b];});
		for(int i=node;i>=2;i--) endpos[fa[id[i]]]=endpos[id[i]];
		ll ans=0;
		for(int i=2;i<=node;i++)
		{
			ans+=len[i]-len[fa[i]];
			ans-=max(0,min(len[i],lsp[endpos[i]])-len[fa[i]]);
		}
		return ans;
	}
}

int main()
{
//	freopen("P4770_1.in","r",stdin);
//	freopen("P4770_1_my.out","w",stdout);
	scanf("%s%d",s+1,&q);
	n=strlen(s+1);
	SAMS::init();
	while(q--)
	{
		scanf("%s",t+1);
		m=strlen(t+1);
		L=read(),R=read();
		SAMS::work();
		printf("%lld\n",SAMT::work());
	}
	return 0;
}
/*
abc
1
abc 3 3
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值