【雅礼集训2017】字符串【后缀自动机】【数据分治】

本文介绍了一种解决字符串匹配问题的高效算法,利用后缀自动机进行数据分治,针对不同情况采用SOLVE1和SOLVE2两种策略,分别处理k小于q和k大于q的情形,实现了对字符串在特定区间内出现次数的有效统计。

题意:给定一个字符串SSSmmm个区间[li,ri][l_i,r_i][li,ri],qqq次询问,每次给定长度为kkk的字符串www和区间[a,b][a,b][a,b],求对于所有i∈[a,b]i\in[a,b]i[a,b]www[li,ri][l_i,r_i][li,ri]内的子串在SSS中出现次数之和。

∣S∣,m,∑∣w∣≤105|S|,m,\sum|w|\leq10^5S,m,w105

看上去很不可做,但是有一个很难注意到的特殊性质:所有www串长相等,所以kq≤105kq\leq10^5kq105。后面记kq=wkq=wkq=w

所以k,qk,qk,q中的较小值是根号级别的,考虑数据分治

首先肯定要先建出SSS的后缀自动机

k<qk<qk<q时,字符串很短,直接开k2k^2k2个vector记录所有区间出现的位置,然后暴力枚举www的子串,在对应的vector用aaabbb二分一下算出有多少个区间,乘上在后缀自动机上的size。复杂度O(qk2log⁡n)=O(wwlog⁡n)O(qk^2\log n)=O(w\sqrt w\log n)O(qk2logn)=O(wwlogn)

k>qk>qk>q时,询问很少,可以每次单独处理。每次读入www后先预处理出www的每个前缀iii 最长的 是SSS的子串 的 后缀长度LiL_iLi

然后暴力把[a,b][a,b][a,b]中的区间挂到rrr上,从左到右扫一遍,设当前处理[l,r][l,r][l,r],如果Lr<r−l+1L_r<r-l+1Lr<rl+1,说明这个子串没有出现过,直接跳过;否则在fail树上倍增找到最靠上的满足lenp≥r−l+1len_p\geq r-l+1lenprl+1的结点ppp,这个子串就出现了sizpsiz_psizp次。复杂度O(qmlog⁡n)=O(mwlog⁡n)O(qm\log n)=O(m\sqrt w\log n)O(qmlogn)=O(mwlogn)

某个k=qk=qk=q的点用SOLVE2会卡常,所以特判成了SOLVE1

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <vector>
#include <algorithm>
#define MAXN 200005
using namespace std;
int ch[MAXN][26],fa[MAXN],tot=1,las=1;
int len[MAXN],siz[MAXN];
void insert(int c)
{
	int p=las,cur=++tot;
	len[cur]=len[las]+1,las=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 _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;
		}
	}
	siz[cur]=1;
}
int a[MAXN],c[MAXN],up[MAXN][20];
inline void build(int n)
{
	for (int i=1;i<=tot;i++) ++c[len[i]];
	for (int i=1;i<=n;i++) c[i]+=c[i-1];
	for (int i=tot;i;i--) a[c[len[i]]--]=i;
	for (int i=1;i<=tot;i++)
	{
		up[a[i]][0]=fa[a[i]];
		for (int j=1;j<20;j++) up[a[i]][j]=up[up[a[i]][j-1]][j-1];
	}
	for (int i=tot;i;i--) if (fa[a[i]]) siz[fa[a[i]]]+=siz[a[i]]; 
}
int n,m,k,q,l[MAXN],r[MAXN];
char s[MAXN],w[MAXN];
typedef long long ll;
namespace SOLVE1
{
	vector<int> lis[405][405];
	int pos[MAXN];
	void main()
	{
		for (int i=1;i<=m;i++) lis[l[i]][r[i]].push_back(i);
		while (q--)
		{
			int a,b;
			scanf("%s%d%d",w+1,&a,&b);
			++a,++b;
			ll ans=0;
			for (int i=1;i<=k;i++)
			{
				int now=1;
				for (int j=i;j<=k;j++)
				{
					now=ch[now][w[j]-'a'];
					if (!now) break;
					ans+=(ll)siz[now]*(upper_bound(lis[i][j].begin(),lis[i][j].end(),b)-upper_bound(lis[i][j].begin(),lis[i][j].end(),a-1));
				}
			}
			printf("%lld\n",ans);
		}
	}
}
namespace SOLVE2
{
	vector<int> lis[MAXN];
	int pos[MAXN],maxl[MAXN];
	void main()
	{
		while (q--)
		{
			int a,b;
			scanf("%s%d%d",w+1,&a,&b);
			++a,++b;
			ll ans=0;
			int now=1,curl=0;
			for (int i=a;i<=b;i++) lis[r[i]].push_back(l[i]);
			for (int i=1;i<=k;i++) 
			{
				while (now&&!ch[now][w[i]-'a']) now=fa[now],curl=len[now];
				now=ch[now][w[i]-'a'],++curl;
				if (!now) now=1,curl=0;
				pos[i]=now,maxl[i]=curl;
			}
			for (int p=1;p<=k;p++)
				for (int j=0;j<(int)lis[p].size();j++)
				{
					int u=pos[p],lim=p-lis[p][j]+1;
					if (maxl[p]<lim) continue;
					for (int i=19;i>=0;i--)
						if (len[up[u][i]]>=lim)
							u=up[u][i];
					ans+=siz[u];
				}
			printf("%lld\n",ans);
			for (int i=a;i<=b;i++) lis[r[i]].clear();
		}
	}
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&q,&k);
	scanf("%s",s+1);
	for (int i=1;i<=n;i++) insert(s[i]-'a');
	build(n);
	for (int i=1;i<=m;i++) scanf("%d%d",&l[i],&r[i]),++l[i],++r[i];
	if (k<=q) 
		SOLVE1::main();
	else
		SOLVE2::main();
	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值