NOI2018 你的名字

你的名字

题目背景

实力强大的小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

说明

测试点 $|S|\leq$ $Q\leq $ $\sum |T|\leq $ 其他限制
1 $200$ $200$ $40000$ $|T|\leq 200$
2 $1000$ $200$ $40000$ $|T|\leq 200$
3 $1000$ $200$ $40000$ $|T|\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=|S|$

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

题解

补集转换一步,问题变成 (\(T\) 的本质不同的子串数) - (\(T\)\(S[l_i:r_i]\) 的本质不同的公共子串数)。

第一个问题是 SAM 模板。

针对第二个问题,我们要找到 \(T\) 的 SAM 上的每个节点 \(x\)\(T\) 中合法长度中 \([len_{fa_x}+1,len_x]\) 和 在 \(S\) 中的合法长度的交集。那么我们便要去找每个节点 \(x\) 能匹配到 \(S[l_i:r_i]\) 中某个串时的最大后缀长度 \(res_x\)

\(len_x\) 可以把 \(T\) 丢到 \(S\) 的 SAM 上去匹配。对于每个 \(T\) 的前缀找到在 \(S\) 中的最长匹配位置,然后不断删去首字母,直到能在 \(S[l_i:r_i]\) 放下。这个用线段树合并维护 right 集合即可。

时间复杂度\(O((|S|+\sum|T|)\log |S|)\)

co int N=2e6;
char s[N];
int res[N],n,q;
// Interval Tree
namespace T{
	int siz[N*25],lc[N*25],rc[N*25],tot;
	void insert(int&x,int l,int r,int p){
		if(!x) x=++tot;
		++siz[x];
		if(l==r) return;
		int mid=l+r>>1;
		if(p<=mid) insert(lc[x],l,mid,p);
		else insert(rc[x],mid+1,r,p);
	}
	int merge(int x,int y){
		if(!x||!y) return x+y;
		int o=++tot;
		siz[o]=siz[x]+siz[y];
		lc[o]=merge(lc[x],lc[y]);
		rc[o]=merge(rc[x],rc[y]);
		return o;
	}
	int query(int x,int l,int r,int ql,int qr){
		if(!x) return 0;
		if(ql<=l&&r<=qr) return siz[x];
		int mid=l+r>>1;
		if(qr<=mid) return query(lc[x],l,mid,ql,qr);
		if(ql>mid) return query(rc[x],mid+1,r,ql,qr);
		return query(lc[x],l,mid,ql,qr)+query(rc[x],mid+1,r,ql,qr);
	}
}
// Suffix Automaton
vector<int> vec[N];
namespace S1{ // S
	vector<int> e[N];
	int last=1,tot=1;
	int ch[N][26],fa[N],len[N],rt[N];
	void extend(int c,int po){
		int p=last,cur=last=++tot;
		T::insert(rt[cur],1,n,po);
		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 clone=++tot;
				memcpy(ch[clone],ch[q],sizeof ch[q]);
				fa[clone]=fa[q],len[clone]=len[p]+1;
				fa[cur]=fa[q]=clone;
				for(;ch[p][c]==q;p=fa[p]) ch[p][c]=clone;
			}
		}
	}
	void dfs(int u){
		for(int i=0;i<e[u].size();++i)
			dfs(e[u][i]),rt[u]=T::merge(rt[u],rt[e[u][i]]);
	}
	void build_tree(){
		for(int i=2;i<=tot;++i) e[fa[i]].push_back(i);
		dfs(1);
	}
	void solve(char*s,int L,int R){
		int length=strlen(s+1);
		for(int i=1,p=1,now=0;i<=length;++i){
			int c=s[i]-'a';
			while(!ch[p][c]&&p) p=fa[p],now=len[p];
			if(!p) {p=1,now=0;continue;}
			p=ch[p][c],++now;
			while(p>1){
				if(T::query(rt[p],1,n,L+now-1,R)) break;
				if(--now==len[fa[p]]) p=fa[p];
			}
			if(p==1) continue;
			for(int j=0;j<vec[i].size();++j)
				res[vec[i][j]]=max(res[vec[i][j]],now);
		}
	}
}
namespace S2{ // T
	int last,tot;
	int ch[N][26],fa[N],len[N];
	void clear(){
		for(int i=1;i<=tot;++i){
			fa[i]=len[i]=res[i]=0;
			memset(ch[i],0,sizeof ch[i]);
		}
		last=tot=1;
	}
	void extend(int c,int po){
		int p=last,cur=last=++tot;
		len[cur]=len[p]+1,vec[po].push_back(cur);
		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 clone=++tot;
				memcpy(ch[clone],ch[q],sizeof ch[q]);
				fa[clone]=fa[q],len[clone]=len[p]+1,vec[po].push_back(clone);
				fa[cur]=fa[q]=clone;
				for(;ch[p][c]==q;p=fa[p]) ch[p][c]=clone;
			}
		}
	}
	void solve(){
		ll ans1=0,ans2=0;
		for(int i=1;i<=tot;++i){
			if(res[i]>len[fa[i]]) ans2+=min(res[i],len[i])-len[fa[i]];
			ans1+=len[i]-len[fa[i]];
		}
		printf("%lld\n",ans1-ans2);
	}
}
int main(){
//	freopen("name.in","r",stdin),freopen("name.out","w",stdout);
	scanf("%s",s+1),n=strlen(s+1);
	for(int i=1;i<=n;++i) S1::extend(s[i]-'a',i);
	S1::build_tree();
	read(q);
	for(int m,L,R;q--;){
		scanf("%s",s+1),m=strlen(s+1);
		read(L),read(R);
		for(int i=1;i<=m;++i) vec[i].clear();
		S2::clear();
		for(int i=1;i<=m;++i) S2::extend(s[i]-'a',i);
		S1::solve(s,L,R),S2::solve();
	}
	return 0;
}

posted on 2019-05-22 17:54  autoint  阅读(191)  评论(0编辑  收藏  举报

导航