【洛谷P4770】你的名字

题目

题目链接:https://www.luogu.com.cn/problem/P4770
小 A 被选为了 ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了。
由于 ION 已经举办了很多届,所以在题目命名上也是有规定的,ION 命题手册规定:每年由命题委员会规定一个小写字母字符串,我们称之为那一年的命名串,要求每道题的名字必须是那一年的命名串的一个非空连续子串,且不能和前一年的任何一道题目的名字相同。
由于一些特殊的原因,小 A 不知道 ION2017 每道题的名字,但是他通过一些特殊手段得到了 ION2017 的命名串,现在小 A 有 \(Q\) 次询问:每次给定 ION2017 的命名串和 ION2018 的命名串,求有几种题目的命名,使得这个名字一定满足命题委员会的规定,即是 ION2018 的命名串的一个非空连续子串且一定不会和 ION2017 的任何一道题目的名字相同。
由于一些特殊原因,所有询问给出的 ION2017 的命名串都是某个串的连续子串,详细可见输入格式。
\(|S|,|T|\leq 5\times 10^5;\sum |T|\leq 10^6;Q\leq 10^5\)

思路

SAM Visualizer 简直是 debug 神器!!!!11
\(n=|S|,m=|T|\)
首先考虑 \(l=1,r=n\) 的部分分。也就是给定 \(S,T\),询问有多少个 \(T\) 本质不同的子串没有在 \(S\) 中出现过。
显然只需要求出有多少个 \(T\) 本质不同的子串在 \(S\) 中出现过,然后用本质不同子串数量减一下即可。
考虑枚举 \(T\) 的每一个位置 \(i\),不难发现以 \(i\) 结尾,且在 \(S\) 中出现过的 \(T\) 的子串一定是右端点为 \(i\) 的一段后缀。我们只需要求出每一个后缀的长度加起来即可。当然还需要保证本质不同。
\(S\)\(T\) 分别建一个 SAM,然后考虑将 \(T\)\(S\) 的后缀自动机上跑匹配。我们知道 parent 树上,一个节点 \(x\) 的父亲表示 \(x\) 所代表等价类最短串删去第一个字符后所代表的等价类。这玩意和 AC 自动机的 fail 十分相似。
假设我们枚举到 \(T\) 的第 \(i\) 位,现在在 \(S\) 的后缀自动机上匹配到的点为 \(x\),那么我们可以不断跳 \(x\) 在 parent 树上的父亲,直到 \(x\) 在后缀自动机上有 \(T_i\) 这条出边为止,那么直接把 \(x\) 赋值为出边所连接的点编号即可。显然这一个点就是与 \(T\)\(i\) 处结尾的子串匹配的等价类。匹配的长度为 \(\text{len}_{\text{fa}[x]}+1\)
那么我们照样在 \(T\) 的后缀自动机上找到 \(T[1:i]\) 所在的等价类,然后把匹配长度记录下来,最后 topsort 一次计算答案即可。
时间复杂度是喜闻乐见的 \(O(n+m)\) 的。


接下来考虑回原题。我们肯定无法快速得到 \(S\) 的一个子串的 SAM,但是观察我们在 \(S\) 的 SAM 上的操作只有以下三种:

  • 跳 parent 树匹配。
  • 判断一个点 \(x\) 是否有字符 \(c\) 的出边。
  • 用点 \(\text{len}_x\) 更新 \(T\) 的后缀自动机的答案。

其中跳 parent 树是不受区间影响的,复杂度只和 \(\sum |T|\) 有关。
判断一个点 \(x\) 是否有字符 \(c\) 的出边则增加一个一个要求:去往的点的 \(\text{endpos}\cap [l,r]\neq \emptyset\)。这个直接可持久化线段树合并维护 \(\text{endpos}\) 即可。
用点 \(\text{len}_x\) 更新 \(T\) 的后缀自动机的答案,因为有了区间限制,我们可能取到的串有一部分前缀会超出区间范围。但是也比较容易处理。我们在线段树上维护区间最大值,要求匹配串长度时就查询 \([l,r]\) 的最大值,因为在一个等价类中,我们要保证能往前选择更多,在右端点肯定不超过范围的前提下,右端点越后能去到的长度越长。具体的,记 \(p\)\(\text{endpos}\cap [l,r]\) 集合中的最大值,能匹配的长度则为 \(\min(\text{len}_{\text{fa}[x]+1,p-l+1})\)
有一点需要注意的是,取了 \(\min\) 之后,能匹配的长度可能就比 \(x\) 等价类中最短子串的长度更短了,此时我们依然需要继续跳 parent 树,因为 \(\text{fa}[x]\) 可能会有更后的 \(\text{endpos}\)
那么我们就解决了区间的问题。时间复杂度 \(O((n+\sum m)\log n)\)

代码

因为一个傻逼错误调了一个下午。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=1000010,LG=20;
int n,m,Q,leng,rt[N];
char s[N],t[N];
ll ans;

struct SegTree
{
	int tot,lc[N*LG*4],rc[N*LG*4],maxp[N*LG*4];
	
	int update(int x,int l,int r,int k,int pos)
	{
		if (!x) x=++tot;
		if (l==r) { maxp[x]=max(maxp[x],pos); return x; }
		int mid=(l+r)>>1;
		if (k<=mid) lc[x]=update(lc[x],l,mid,k,pos);
			else rc[x]=update(rc[x],mid+1,r,k,pos);
		maxp[x]=max(maxp[lc[x]],maxp[rc[x]]);
		return x;
	} 
	
	int merge(int x,int y)
	{
		if (!x || !y) return x|y;
		int p=++tot;
		maxp[p]=max(maxp[x],maxp[y]);
		lc[p]=merge(lc[x],lc[y]);
		rc[p]=merge(rc[x],rc[y]);
		return p;
	}
	
	int query(int x,int l,int r,int ql,int qr)
	{
		if (ql<=l && qr>=r) return maxp[x];
		int mid=(l+r)>>1,res=0;
		if (ql<=mid) res=max(res,query(lc[x],l,mid,ql,qr));
		if (qr>mid) res=max(res,query(rc[x],mid+1,r,ql,qr));
		return res;
	}
}seg;

struct SAM
{
	int last,tot,len[N],fa[N],ch[N][26],res[N],wyctql[N],xjqtql[N];
	
	void init()
	{
		for (int i=0;i<=tot;i++)
		{
			len[i]=fa[i]=wyctql[i]=xjqtql[i]=res[i]=0;
			for (int j=0;j<26;j++) ch[i][j]=0;
		}
		last=tot=1;
	}
	
	void ins(int c,int k)
	{
		int p=last,np=++tot;
		last=np; len[np]=len[p]+1;
		if (k!=-1) rt[np]=seg.update(rt[np],1,n,k,k);
		for (;p && !ch[p][c];p=fa[p]) ch[p][c]=np;
		if (!p) fa[np]=1;
		else
		{
			int q=ch[p][c];
			if (len[q]==len[p]+1) fa[np]=q;
			else
			{
				int nq=++tot;
				fa[nq]=fa[q]; len[nq]=len[p]+1;
				for (int i=0;i<26;i++) ch[nq][i]=ch[q][i];
				fa[q]=fa[np]=nq;
				for (;p && ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
			}
		}
	}
	
	void topsort(bool type)
	{
		for (int i=1;i<=tot;i++) xjqtql[len[i]]++;
		for (int i=1;i<=tot;i++) xjqtql[i]+=xjqtql[i-1];
		for (int i=1;i<=tot;i++) wyctql[xjqtql[len[i]]--]=i;
		for (int i=tot;i>=1;i--)
		{
			int x=wyctql[i];
			if (!type) rt[fa[x]]=seg.merge(rt[fa[x]],rt[x]);
			else
			{
				res[fa[x]]=max(res[fa[x]],res[x]);
				res[x]=min(res[x],len[x]);
				if (x>1) ans+=len[x]-max(len[fa[x]],res[x]);
			}
		}
	}
	
	int find(int x,int c,int l,int r)
	{
		for (;x;x=fa[x],leng=len[x])
		{
			if (!ch[x][c]) continue;
			int p=seg.query(rt[ch[x][c]],1,n,l,r);
			if (p && p-l+1>len[fa[ch[x][c]]])
			{
				leng=min(leng+1,p-l+1);
				return ch[x][c];
			}
		}
		return 1;
	}
}sam1,sam2;

void solve(int l,int r)
{
	leng=0;
	for (int i=1,p1=1,p2=1;i<=m;i++)
	{
		p1=sam1.find(p1,t[i]-'a',l,r);
		p2=sam2.ch[p2][t[i]-'a'];
		sam2.res[p2]=leng;
	}
}

int main()
{
	scanf("%s",s+1);
	n=strlen(s+1);
	sam1.init();
	for (int i=1;i<=n;i++)
		sam1.ins(s[i]-'a',i);
	sam1.topsort(0);
	scanf("%d",&Q);
	while (Q--)
	{
		int l,r; ans=0;
		scanf("%s%d%d",t+1,&l,&r);
		m=strlen(t+1);
		sam2.init();
		for (int i=1;i<=m;i++)
			sam2.ins(t[i]-'a',-1);
		solve(l,r);
		sam2.topsort(1);
		printf("%lld\n",ans);
	}
	return 0;
}
posted @ 2021-05-20 22:54  stoorz  阅读(78)  评论(0编辑  收藏  举报