【NOI2018】你的名字【后缀自动机】【可持久化线段树合并】【乱搞】

博客围绕串匹配问题展开,给定串 S 和 q 次询问,每次询问包含串 T 及区间 l,r ,需计算 T 中本质不同且非 Sl…r 子串的数量。先处理 l=1,r=∣S∣ 的情况,借助后缀自动机(SAM)等方法,再用可持久化线段树合并处理 l,r 任意的情况,复杂度为 O(nlogn)。

题意:给一个串 SSSqqq 次询问,每次给定串 TTTl,rl,rl,r ,求有多少个本质不同的串是 TTT 的子串而不是 Sl…rS_{l\dots r}Slr 的子串。

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

先考虑所有询问 l=1,r=∣S∣l=1,r=|S|l=1,r=S 怎么做。

先对 SSS 建个 SAM 出来,我们需要把每次询问的 TTT 放上去搞搞。如果用广义后缀自动机的话复杂度是和 SSS 有关的,做不了。所以要把 TTT 就看成一个串。

考虑把 TTT 放上去匹配,从 SAM 上的当前点不断跳 fail 直到有对应的转移边。这样可以得到 TTT 的每一个前缀 [1,i][1,i][1,i] 最长的 在 SSS 中出现过的 后缀长度,记为 lenilen_ileni

但我们要求的是本质不同的串,还需要把上面那个东西去重。

我们一次加入 TTT 的每个字符,那么当前的一个后缀合法当且仅当其长度大于 lenilen_ileni。我们可以对 TTT 再单独建一个 SAM,维护每个结点 endpos 集合中的最大值,然后遍历每个结点统计答案即可。

然后对于 l,rl,rl,r 任意的情况,就是用可持久化线段树合并来显式维护 endpos 集合。

然后就是一通乱搞。

冷静分析一下,我们要做的是当前维护的串是否在 Sl…rS_{l\dots r}Slr 中出现过,也就是 [l+s−1,r][l+s-1,r][l+s1,r] 中有没有这个点的 endpos 集合中的位置,其中 sss 为当前串长度。直接暴力丢掉第一个字符,如果当前结点丢完了再跳父亲。转移的时候额外判一下要去的结点对应区间有没有这个点的 endpos。

因为这个串可能被砍了一半,所以在更新 lenilen_ileni 的时候要在 [l,r][l,r][l,r] 里查一个 endpos 的最大值 mxmxmx,和 mx−l+1mx-l+1mxl+1min⁡\minmin

复杂度 O(nlog⁡n)\Omicron(n\log n)O(nlogn)

#include <iostream>
#include <cstring>
#include <cctype>
#include <cstdio>
#define MAXN 2000005
using namespace std;
typedef long long ll;
int ch[MAXN<<4][2],sum[MAXN<<4],mxpos[MAXN<<4],cnt;
void modify(int& x,int l,int r,int k)
{
	if (!x) x=++cnt;
	++sum[x];
	if (l==r) return (void)(mxpos[x]=l);
	int mid=(l+r)>>1;
	if (k<=mid) modify(ch[x][0],l,mid,k);
	else modify(ch[x][1],mid+1,r,k);
	mxpos[x]=max(mxpos[ch[x][0]],mxpos[ch[x][1]]);
}
int merge(int x,int y)
{
	if (!x||!y) return x|y;
	int p=++cnt;
	ch[p][0]=ch[x][0],ch[p][1]=ch[x][1];
	ch[p][0]=merge(ch[p][0],ch[y][0]);
	ch[p][1]=merge(ch[p][1],ch[y][1]);
	sum[p]=sum[ch[p][0]]+sum[ch[p][1]];
	mxpos[p]=max(mxpos[ch[p][0]],mxpos[ch[p][1]]);
	return p;
}
int query(int x,int l,int r,int ql,int qr)
{
	if (!x) return 0;
	if (ql<=l&&r<=qr) return sum[x];
	if (qr<l||r<ql) return 0;
	int mid=(l+r)>>1;
	return query(ch[x][0],l,mid,ql,qr)+query(ch[x][1],mid+1,r,ql,qr);
}
int querymax(int x,int l,int r,int ql,int qr)
{
	if (!x) return 0;
	if (ql<=l&&r<=qr) return mxpos[x];
	if (qr<l||r<ql) return 0;
	int mid=(l+r)>>1;
	return max(querymax(ch[x][0],l,mid,ql,qr),querymax(ch[x][1],mid+1,r,ql,qr));
}
int a[MAXN],c[MAXN],pos[MAXN],n,m;
char s[MAXN],t[MAXN];
struct SAM
{
	int ch[MAXN][26],fa[MAXN],len[MAXN],rt[MAXN],mx[MAXN],las,tot;
	inline void insert(int c,int k,int type)
	{
		int cur=++tot;
		int p=las;
		len[cur]=len[p]+1;
		for (;p&&!ch[p][c];p=fa[p]) ch[p][c]=cur;
		if (!p) fa[cur]=1;
		else
		{
			int q=ch[p][c];
			if (len[q]==len[p]+1) fa[cur]=q;
			else
			{
				int _q=++tot;
				len[_q]=len[p]+1;
				fa[_q]=fa[q],fa[q]=fa[cur]=_q;
				memcpy(ch[_q],ch[q],sizeof(ch[_q]));
				for (;ch[p][c]==q;p=fa[p]) ch[p][c]=_q;
			}
		}
		las=cur;
		if (type) modify(rt[cur],1,n,k);
		else mx[cur]=k;
	}
	inline void build(int type)
	{
		for (int i=1;i<=tot;i++) c[i]=0;
		for (int i=1;i<=tot;i++) ++c[len[i]];
		for (int i=1;i<=tot;i++) c[i]+=c[i-1];
		for (int i=1;i<=tot;i++) a[c[len[i]]--]=i;
		for (int i=tot;i>=1;i--) 
			if (fa[a[i]]) 
			{
				if (type) rt[fa[a[i]]]=merge(rt[fa[a[i]]],rt[a[i]]);
				else mx[fa[a[i]]]=max(mx[fa[a[i]]],mx[a[i]]);				
			}
	}
	inline void query(int l,int r)
	{
		int cur=1,s=0,p=0;
		for (int i=1;i<=m;i++)
		{
			while (cur>1&&(!ch[cur][t[i]-'a']||l+s-1>r||!::query(rt[ch[cur][t[i]-'a']],1,n,l+max(s,1)-1,r)))
				((--s)==len[fa[cur]])&&(cur=fa[cur]);
			(::query(rt[ch[cur][t[i]-'a']],1,n,l+max(s,1)-1,r))&&(cur=ch[cur][t[i]-'a'],++s,++p);
			pos[i]=max(0,min(s,querymax(rt[cur],1,n,l,r)-l+1));
		}
	}
	inline ll calc()
	{
		ll ans=0;
		for (int i=1;i<=tot;i++) ans+=max(0,len[i]-max(len[fa[i]],pos[mx[i]]));
		return ans;
	}
	inline void clear()
	{
		for (int i=1;i<=tot;i++) mx[i]=fa[i]=0,memset(ch[i],0,sizeof(ch[i]));
		las=tot=1;
	}
	inline SAM():las(1),tot(1){}	
}S,T;
int main()
{
	scanf("%s",s+1);
	n=strlen(s+1);
	for (int i=1;i<=n;i++) S.insert(s[i]-'a',i,1);
	S.build(1);
	int q;
	scanf("%d",&q);
	while (q--)
	{
		scanf("%s",t+1);
		m=strlen(t+1);
		for (int i=1;i<=m;i++) T.insert(t[i]-'a',i,0);
		T.build(0);
		int l,r;
		scanf("%d%d",&l,&r);
		S.query(l,r);
		printf("%lld\n",T.calc());
		T.clear();
	}
	return 0;
}
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值