codeforces 587F. Duff is Mad

4 篇文章 0 订阅
2 篇文章 0 订阅

蛮好的一道fail树的题目

考虑fail树,对于询问l,r,k,相当于询问l~r的字符串在AC自动机上对应节点在fail树子树中是第k个串前缀的节点的个数和。

发现k固定有一种O(n+Σ串长)的优秀做法,就不说了。同时对一些询问存在使用分块数据结构维护的离线O(Σ询问串长)的做法,就可以分块了,复杂度O(nsort(n))

这个离线O(Σ询问串长)做法把询问挂在fail树上,dfs的同时维护到根节点的字符串中点节点,遇到串的前缀即询问,就更新答案,由于这部分处理的串长均小于sqrt(n)所以询问节点共nsort(n),使用O(sqrt(n))修改,O(1)查询分块,达到O(nsqrt(n))。

UPD:估计没人能看懂这样的题解,有时间再写一份(flag

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
#define N 100005
#define ll long long
using namespace std;
int n,l[N],r[N],bl[N],SZ=1000,R[N],L[N],K[N],cnt,sz=1000,Q,sum;
int nod,v[N][26],fail[N],q[N],fa[N],ss[N],ly[N];ll vv[N],val[N],ans[N];
char s[N];
vector<int>a[N],b[N],c[N],d[N];
void mdy(int x,int y)
{
	for (int i=bl[x]+1;i<=sum;i++)
		val[i]+=y;
	for (int i=x;i<=bl[x]*SZ;i++)
		vv[i]+=y;
}
ll qry(int x)
{
	if (x==0) return 0;
	return val[bl[x]]+vv[x];
}
void dfs(int x)
{
	for (int i=0;i<a[x].size();i++)
		mdy(a[x][i],1);
	for (int i=0,t;i<b[x].size();i++)
	{
		for (int j=0;j<d[b[x][i]].size();j++)
		{
			t=d[b[x][i]][j];
			ans[t]+=qry(R[t])-qry(L[t]-1);
		}
	}
	for (int i=0;i<c[x].size();i++)
		dfs(c[x][i]);
	for (int i=0;i<a[x].size();i++)
		mdy(a[x][i],-1);
}
void build()
{
	for (int i=0;i<26;i++)
		v[0][i]=1;
	int l=0,r=1;
	q[1]=1;
	while(l<r)
	{
		int x=q[++l];
		if (x==1) fail[1]=0;
		else fail[x]=v[fail[fa[x]]][ss[x]],c[fail[x]].push_back(x);
		for (int i=0;i<26;i++)
			if (v[x][i]) q[++r]=v[x][i];
			else v[x][i]=v[fail[x]][i];
	}
}
void Dfs(int x)
{
	for (int i=0;i<c[x].size();i++)
	{
		Dfs(c[x][i]);
		ly[x]+=ly[c[x][i]];
	}
	for (int i=0;i<a[x].size();i++)
		val[a[x][i]]+=ly[x];
}
//a:终止节点 
//b:fail节点 
//c:son节点 
//d:update节点 
int main()
{
	scanf("%d%d",&n,&Q);
	nod=1;
	for (int i=1;i<=n;i++)
	{
		l[i]=cnt+1;
		char c=getchar();
		while(c<'a'||c>'z')c=getchar();
		while(c>='a'&&c<='z')s[++cnt]=c-'a',c=getchar();
		r[i]=cnt;
		int now=1;
		for (int j=l[i];j<=r[i];j++)
		{
			if (!v[now][s[j]]) v[now][s[j]]=++nod,fa[nod]=now,ss[nod]=s[j];
			now=v[now][s[j]];
			if (r[i]-l[i]+1<=sz) b[now].push_back(i);
		}
		a[now].push_back(i);
	}
	build();
	for (int i=1;i<=n;i++)
		bl[i]=(i+SZ-1)/SZ;
	sum=bl[n];
	for (int i=1;i<=Q;i++)
	{
		scanf("%d%d%d",&L[i],&R[i],&K[i]);
		d[K[i]].push_back(i);
	}
	dfs(1);
	for (int i=1;i<=n;i++)
	if (r[i]-l[i]+1>sz)
	{
		memset(ly,0,sizeof ly);
		memset(val,0,sizeof val);
		int now=1;
		for (int j=l[i];j<=r[i];j++)
		{
			now=v[now][s[j]];
			ly[now]++;
		}
		Dfs(1);
		for (int j=1;j<=n;j++)
			val[j]+=val[j-1];
		for (int j=0;j<d[i].size();j++)
			ans[d[i][j]]=val[R[d[i][j]]]-val[L[d[i][j]]-1];
	}
	for (int i=1;i<=Q;i++)
		printf("%lld\n",ans[i]); 
	
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值