【51nod 1600】Simple KMP
题目
题目链接:http://www.51nod.com/Challenge/Problem.html#problemId=1600
定义 \(f(S)\) 表示字符串 \(S\) fail 树所有节点深度之和。\(g(S)\) 表示 \(S\) 所有字串的 \(f\) 之和。
给定一个字符串 \(S\),求对于 \(j\in[1,|S|]\)。
\(|S|\leq 2\times 10^5\)。
思路
考虑对于一个字符串 \(s\) 的一段前缀 \(s[1:i]\),它的贡献就是满足 \(s[1:j]=s[i-j+1:i]\) 的 \(j\) 的数量。
那么这个前缀 \(s[1:i]\) 的每一个贡献会被计算 \(|S|-i+1\) 次。因为 \(f(s[1:k])(k\geq i)\) 都会计算到这个贡献。
再考虑在字符串末尾添加一个字符的贡献,原来的每一个贡献都新造成了 \(1\) 的贡献(上一行可选取的 \(k\) 多了一个),以及插入这个字符后字符串所有后缀的贡献。
所以我们只需要知道每插入一个字符之后字符串所有后缀的贡献之和。
考虑对整个字符串 \(s\) 建立 SAM,假设插入第 \(i\) 个字符后,\(s[1:i]\) 所在的类是 SAM 上编号为 \(x\) 的点,那么它的贡献就是
其中 \(\text{cnt}_y\) 表示点 \(y\) 所代表等价类在 \(s[1:i-1]\) 中出现了多少次。不难发现只有 \(x\) 的祖先会造成贡献。
考虑用树剖维护树上的区间和以及区间修改。线段树上每一个区间维护这个区间的所有等价类 \(\text{len}_x-\text{len}_{fa_x}\) 的和,以及 \((\text{len}_x-\text{len}_{fa_x})\times \text{cnt}_x\) 的和。每当我们找到需要查询的区间 \([l,r]\) 时,记录下答案后可以直接顺便修改(修改显然是 \(\text{cnt}\gets \text{cnt}+1\),可以直接维护和而不是 \(\text{cnt}\))。然后再返回记录下的答案。
求出来之后再求一遍前缀和即可。
时间复杂度 \(O(n\log^2 n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=400010,MOD=1e9+7;
int n,tot,head[N],num[N],fa[N],top[N],id[N],rk[N],son[N],dep[N],siz[N],f[N];
char s[N];
struct edge
{
int next,to;
}e[N];
void add(int from,int to)
{
e[++tot]=(edge){head[from],to};
head[from]=tot;
}
struct SAM
{
int tot,last,len[N],fa[N],ch[N][26];
SAM() { tot=last=1; }
int ins(int c)
{
int np=++tot,p=last; last=np;
len[np]=len[p]+1;
for (;!ch[p][c];p=fa[p]) ch[p][c]=np;
if (!p) fa[np]=1;
else
{
int q=ch[p][c];
if (len[q]==len[p]+1) fa[np]=q;
else
{
int nq=++tot;
fa[nq]=fa[q]; len[nq]=len[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
fa[q]=fa[np]=nq;
for (;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
return last;
}
}sam;
void dfs1(int x,int pa)
{
dep[x]=dep[pa]+1; fa[x]=pa; siz[x]=1;
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
dfs1(v,x);
siz[x]+=siz[v];
if (siz[v]>siz[son[x]]) son[x]=v;
}
}
void dfs2(int x,int tp)
{
top[x]=tp; id[x]=++tot; rk[tot]=x;
if (son[x]) dfs2(son[x],tp);
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (v!=son[x]) dfs2(v,v);
}
}
struct SegTree
{
int sum[N*4],val[N*4],lazy[N*4];
void pushup(int x)
{
val[x]=(val[x*2]+val[x*2+1])%MOD;
sum[x]=(sum[x*2]+sum[x*2+1])%MOD;
}
void pushdown(int x)
{
if (lazy[x])
{
sum[x*2]=(sum[x*2]+1LL*val[x*2]*lazy[x])%MOD;
sum[x*2+1]=(sum[x*2+1]+1LL*val[x*2+1]*lazy[x])%MOD;
lazy[x*2]=(lazy[x*2]+lazy[x])%MOD;
lazy[x*2+1]=(lazy[x*2+1]+lazy[x])%MOD;
lazy[x]=0;
}
}
void build(int x,int l,int r)
{
if (l==r)
{
val[x]=sam.len[rk[l]]-sam.len[sam.fa[rk[l]]];
return;
}
int mid=(l+r)>>1;
build(x*2,l,mid); build(x*2+1,mid+1,r);
pushup(x);
}
int query(int x,int l,int r,int ql,int qr)
{
if (ql<=l && qr>=r)
{
int ans=sum[x];
sum[x]=(sum[x]+val[x])%MOD;
lazy[x]=(lazy[x]+1)%MOD;
return ans;
}
pushdown(x);
int mid=(l+r)>>1,ans=0;
if (ql<=mid) ans=(ans+query(x*2,l,mid,ql,qr))%MOD;
if (qr>mid) ans=(ans+query(x*2+1,mid+1,r,ql,qr))%MOD;
pushup(x);
return ans;
}
}seg;
int query(int x)
{
int ans=0;
for (;x;x=fa[top[x]])
ans=(ans+seg.query(1,1,2*n,id[top[x]],id[x]))%MOD;
return ans;
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%s",&n,s+1);
for (int i=1;i<=n;i++)
num[i]=sam.ins(s[i]-'a');
for (int i=1;i<=2*n;i++)
if (sam.fa[i]) add(sam.fa[i],i);
tot=0; dfs1(1,0); dfs2(1,1);
seg.build(1,1,2*n);
for (int i=1;i<=n;i++)
f[i]=(f[i-1]+query(num[i]))%MOD;
for (int i=1;i<=n;i++)
{
f[i]=(f[i]+f[i-1])%MOD;
printf("%d\n",f[i]);
}
return 0;
}