[NOI2018]你的名字

Posted by Dispwnl on January 9, 2019

题目

题目背景

实力强大的小A 被选为了ION2018 的出题人,现在他需要解决题目的命名问题。

题目描述

小A 被选为了ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了。

由于ION 已经举办了很多届,所以在题目命名上也是有规定的,ION 命题手册规定:每年由命题委员会规定一个小写字母字符串,我们称之为那一年的命名串,要求每道题的名字必须是那一年的命名串的一个非空连续子串,且不能和前一年的任何一道题目的名字相同。

由于一些特殊的原因,小A 不知道ION2017 每道题的名字,但是他通过一些特殊手段得到了ION2017 的命名串,现在小A 有Q 次询问:每次给定ION2017 的命名串和ION2018 的命名串,求有几种题目的命名,使得这个名字一定满足命题委员会的规定,即是ION2018 的命名串的一个非空连续子串且一定不会和ION2017 的任何一道题目的名字相同。

由于一些特殊原因,所有询问给出的ION2017 的命名串都是某个串的连续子串,详细可见输入格式。

输入输出格式

输入格式:

第一行一个字符串S ,之后询问给出的ION2017 的命名串都是S 的连续子串。 第二行一个正整数Q,表示询问次数。 接下来Q 行,每行有一个字符串T 和两个正整数$l,r$,表示询问如果ION2017 的 命名串是$S[l..r]$,ION2018 的命名串是T的话,有几种命名方式一定满足规定。

输出格式:

输出Q 行,第i 行一个非负整数表示第i 个询问的答案。

输入输出样例

输入样例#1:

scbamgepe
3
smape 2 7
sbape 3 8
sgepe 1 9

输出样例#1:

12
10
4

说明

测试点 $\vert S\vert \leq$ $\vert Q \vert \leq$ $\sum \vert T\vert \leq$ 其他限制
1 $200$ $200$ $40000$ $\vert T\vert \leq 200$
2 $1000$ $200$ $40000$ $\vert T\vert \leq 200$
3 $1000$ $200$ $40000$ $\vert T\vert \leq 200$
4 $1000$ $200$ $5 \times 10^5$
5 $1000$ $200$ $5 \times 10^5$
6 $5 \times 10^5$ $1$ $5 \times 10^5$
7 $5 \times 10^5$ $1$ $5 \times 10^5$
8 $10^5$ $10^5$ $2 \times 10^5$
9 $10^5$ $10^5$ $2 \times 10^5$ 字符串随机
10 $2 \times 10^5$ $10^5$ $4 \times 10^5$
11 $2 \times 10^5$ $10^5$ $4 \times 10^5$ 字符串随机
12 $3 \times 10^5$ $10^5$ $6 \times 10^5$
13 $3 \times 10^5$ $10^5$ $6 \times 10^5$ 字符串随机
14 $4 \times 10^5$ $10^5$ $8 \times 10^5$
15 $4 \times 10^5$ $10^5$ $8 \times 10^5$ 字符串随机
16 $5 \times 10^5$ $10^5$ $10^6$
17 $5 \times 10^5$ $10^5$ $10^6$ 字符串随机
18 $2 \times 10^5$ $10^5$ $10^6$
19 $3 \times 10^5$ $10^5$ $10^6$
20 $4 \times 10^5$ $10^5$ $10^6$
21 $5 \times 10^5$ $10^5$ $10^6$
22 $5 \times 10^5$ $10^5$ $10^6$
23 $5 \times 10^5$ $10^5$ $10^6$
24 $5 \times 10^5$ $10^5$ $10^6$
25 $5 \times 10^5$ $10^5$ $10^6$

对于前17个测试点的所有询问有$l=1,r=\vert S\vert$

对于所有数据,保证 $1\leq l \leq r \leq \vert S\vert,1\leq \vert T\vert \leq 5 \times 10^5$

题解

因为$l=1,r=\vert S\vert$有$68$分,所以先考虑这个怎么做

设$Lmax_i$表示$i$往前最多能在$S$里匹配的长度,显然的是$SAM$里一个状态的$Lmax_i$都是相等的

所以能使用的$T$的不同子串个数为$T$串中的$\sum_{i=1}^{l}len_i-max(len_{fa_i},Lmax_{pos_x}),x\in right_i$

现在考虑怎么处理普通的$l,r$情况

发现我们需要记录每个状态的$right$集合,然后匹配的时候查询在匹配区间里是否有位置属于该状态的$right$集合

用线段树合并即可维护,注意匹配的时候匹配区间应该随着匹配长度变化而变化,所以匹配不到时不能直接跳$father$

代码

# include<iostream>
# include<cstring>
# include<cstdio>
# include<algorithm>
# define tl s[k].l
# define tr s[k].r
# define mid (l+r>>1)
# define LL long long
using namespace std;
const int MAX=1e6+5;
struct p{
	int l,r;
}s[MAX<<5];
int n,Q,tot;
int rt[MAX],L_max[MAX],pos[MAX];
char a[MAX];
void change(int l,int r,int &k,int x)
{
	if(!k) k=++tot;
	if(l==r) return;
	if(x<=mid) change(l,mid,tl,x);
	else change(mid+1,r,tr,x);
}
int merge(int x,int y)
{
	if(!x||!y) return x+y;
	int k=++tot;
	return tl=merge(s[x].l,s[y].l),tr=merge(s[x].r,s[y].r),k;
}
bool ask(int l,int r,int k,int L,int R)
{
	if(R<l||r<L||!k||L>R) return 0;
	if(l>=L&&r<=R) return 1;
	return (ask(l,mid,tl,L,R)|ask(mid+1,r,tr,L,R));
}
struct SAM{
	int l,r,L;
	int len[MAX],fa[MAX],id[MAX],tax[MAX];
	int son[MAX][26];
	SAM() {l=r=1;}
	void ins(int x,int id,bool fl=0)
	{
		int tt=r;
		len[r=++l]=++L;
		if(!fl) change(1,n,rt[r],id);
		else pos[r]=id;
		for(;tt&&!son[tt][x];tt=fa[tt])
		  son[tt][x]=r;
		if(!tt) return void(fa[r]=1);
		int qwq=son[tt][x];
		if(len[qwq]==len[tt]+1) return void(fa[r]=qwq);
		len[++l]=len[tt]+1,fa[l]=fa[qwq],fa[qwq]=fa[r]=l;
		for(int i=0;i<26;++i)
		  son[l][i]=son[qwq][i];
		for(int i=tt;son[i][x]==qwq;i=fa[i])
		  son[i][x]=l;
	}
	void Solve(int _n,int l,int r)
	{
		for(int i=1,x=1,_L=0;i<=_n;++i)
		  {
		  	if(son[x][a[i]-'a']&&ask(1,n,rt[son[x][a[i]-'a']],l+_L,r)) x=son[x][a[i]-'a'],++_L;
		  	else
		  	{
		  		while(x&&(!son[x][a[i]-'a']||!ask(1,n,rt[son[x][a[i]-'a']],l+_L,r)))
				{
					if(!_L) {x=0;break;}
				  	--_L;
				  	if(_L==len[fa[x]]) x=fa[x];
				}
		  		if(!x) x=1,_L=0;
		  		else ++_L,x=son[x][a[i]-'a'];
			}
			L_max[i]=_L;
		  }
	}
	void mem()
	{
		for(int i=1;i<=l;++i)
		  {
		  	tax[i]=pos[i]=0;
		  	for(int j=0;j<26;++j)
			  son[i][j]=0;
		  }
		l=r=1,L=0;
	}
	void Init(int _n)
	{
		for(int i=1;i<=l;++i)
		  ++tax[len[i]];
		for(int i=1;i<=_n;++i)
		  tax[i]+=tax[i-1];
		for(int i=l;i>=1;--i)
		  id[tax[len[i]]--]=i;
	}
	void GET_MERGE()
	{
		for(int i=l;i>=1;--i)
		  if(fa[id[i]]) rt[fa[id[i]]]=merge(rt[fa[id[i]]],rt[id[i]]);
	}
	void GET_POS()
	{
		for(int i=l;i>=1;--i)
		  pos[fa[id[i]]]=max(pos[fa[id[i]]],pos[id[i]]);
	}
}_S,__S;
LL GET_ANS(int _n)
{
	LL ans=0;
	__S.Init(_n),__S.GET_POS();
	for(int i=2;i<=__S.l;++i)
	  ans+=__S.len[i]-min(__S.len[i],max(__S.len[__S.fa[i]],L_max[pos[i]]));
	return ans;
}
int main()
{
	scanf("%s",a+1),n=strlen(a+1);
	for(int i=1;i<=n;++i)
	  _S.ins(a[i]-'a',i);
	_S.Init(n),_S.GET_MERGE(),scanf("%d",&Q);
	for(int i=1,_n,l,r;i<=Q;++i)
	  {
	  	scanf("%s%d%d",a+1,&l,&r);
		__S.mem(),_n=strlen(a+1);
		for(int j=1;j<=_n;++j)
		  __S.ins(a[j]-'a',j,1);
		_S.Solve(_n,l,r),printf("%lld\n",GET_ANS(_n));
	  }
	return 0;
}