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

题意:

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

\(|S|\leq 5\times 10^5\)\(q\leq 10^5\)\(\sum |T|\leq 10^6\)

题解:

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

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

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

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

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

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

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

#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
*/
posted @ 2022-10-29 11:05  ez_lcw  阅读(33)  评论(0)    收藏  举报