Description
有n只熊,从1到n进行编号。
第i只熊的电话号码是si。每只熊会给那些电话号码是他的子串的熊打电话(可能会给自己打)。
call(i, j) 表示第i只熊给第j只熊打电话的次数,也就是第j个串在第i个串中出现的次数。
迈克会有q次询问。每个询问中给出l,r,k,然后请您计算一下 ∑ri=lcall(i,k)
Solution
这题的第一反应就是SA,同样SA做也很好做,直接排完序后做主席树,
但是常数太大,怎么办,
考虑一下,发现子串,也就是每个串的前缀的后缀,那么是不是可以用trie来搞,
我们要统计的是每个点到根这条路径上的所有字母,作为后缀在trie中出现了多少次,那么用AC自动机的话刚好可以,因为它的Fail链就指向它所有的后缀,
这样,做完AC自动机后,按Fail的指向连边,这样就变成了求一个点子树的问题了,用DFS序+主席树即可,
但是,我们发现,51NOD的老爷机根本跑不动,怎么办,
考虑优化,这题是可以离线的,那么就离线以后,用树状数组做即可优化常数,
复杂度:O(nlog(n))
Code
#include <cstdio>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define efo(i,q) for(int i=A[q];i;i=B[i][0])
using namespace std;
const int N=200500;
int read(int &n)
{
char ch=' ';int q=0,w=1;
for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
if(ch=='-')w=-1,ch=getchar();
for(;ch>='0' && ch<='9';ch=getchar())q=q*10+ch-48;n=q*w;return n;
}
int m,n,n1,ans;
int a[N],d[N];
struct qqww
{
int a[26],nx,fa,v,A,i,si;
}tp[N];
int dfn,root[N];
struct qwqw
{
int l,r,v;
}b[N*30];
int B1[N*2][2],B10,b0;
int B[N][2],B0,A[N];
void link1(int q,int w){B1[++B10][0]=tp[q].A,tp[q].A=B10,B1[B10][1]=w;}
void link(int q,int w){B[++B0][0]=A[q],A[q]=B0,B[B0][1]=w;}
int build(int I)
{
int q=1,e=1;
for(;;)
{
link1(q,I);
if(e>n1)return q;
if(!tp[q].a[d[e]])
{
tp[q].a[d[e]]=++b0;
tp[b0].fa=q,tp[b0].v=d[e];
}
q=tp[q].a[d[e]];
e++;
}
}
void AC_Auto()
{
int S=1,T=0;
fo(i,0,25)if(tp[1].a[i])
{
fo(j,0,25)if(tp[tp[1].a[i]].a[j])d[++T]=tp[tp[1].a[i]].a[j];
tp[tp[1].a[i]].nx=1;
link(1,tp[1].a[i]);
}
for(;S<=T;S++)
{
int q=d[S],t,w;w=tp[q].v;
for(t=tp[tp[q].fa].nx;t&&!tp[t].a[w];t=tp[t].nx);
tp[q].nx=t?(tp[t].a[w]):1;
link(tp[q].nx,q);
fo(i,0,25)if(tp[q].a[i])d[++T]=tp[q].a[i];
}
}
void change(int l,int r,int e1,int &e,int l1)
{
if(!e||e==e1)b[e=++b0]=b[e1];
if(l==r){b[e].v++;return;}
int t=(l+r)>>1;
if(l1<=t)change(l,t,b[e1].l,b[e].l,l1);
else change(t+1,r,b[e1].r,b[e].r,l1);
b[e].v++;
}
int dfs(int q)
{
tp[q].i=++dfn;tp[q].si=1;
if(q-1)for(int i=tp[q].A;i;i=B1[i][0])change(1,n,root[dfn-1],root[dfn],B1[i][1]);
efo(i,q)tp[q].si+=dfs(B[i][1]);
return tp[q].si;
}
int find(int l,int r,int e1,int e,int l1,int r1)
{
if(e1==e)return 0;
if(l1<=l&&r<=r1)return b[e].v-b[e1].v;
int t=(l+r)>>1;
if(r1<=t)return find(l,t,b[e1].l,b[e].l,l1,r1);
else if(t<l1)return find(t+1,r,b[e1].r,b[e].r,l1,r1);
else return find(l,t,b[e1].l,b[e].l,l1,t)+find(t+1,r,b[e1].r,b[e].r,t+1,r1);
}
int main()
{
int q,w,e;
read(n),read(m);
b0=1;
fo(i,1,n)
{
char ch=' ';
for(;ch<'a'||ch>'z';ch=getchar());
n1=0;
for(;ch<='z'&&ch>='a';ch=getchar())d[++n1]=ch-'a';
a[i]=build(i);
}
AC_Auto();
b0=0;
dfs(1);
fo(i,1,m)
{
read(q),read(w),read(e);e=a[e];
printf("%d\n",find(1,n,root[tp[e].i-1],root[tp[e].i+tp[e].si-1],q,w));
}
return 0;
}
本文介绍了一种使用AC自动机解决子串查询问题的方法。对于给定的多个字符串,构建AC自动机以高效查询特定子串出现的次数。通过离线处理和树状数组优化,实现O(nlog(n))的时间复杂度。
347

被折叠的 条评论
为什么被折叠?



