51nod 1600 Simple KMP
题意
考虑转化下:
我们考虑的树上的每一个点,如果到的路径上有个点(除去和),那么就会对产生的贡献,于是现在变为了每个点会对很多点产生的贡献,最后求和。
我们知道每个点代表一个前缀,那么我们就可以转化成每个前缀的贡献若干个。易知,如果一个前缀在中出现了次(除了它本身),那么就会产生的贡献。
我们再来想:
。
我们统计每个子串的贡献,假如有两个子串相等,那么就会产生的贡献。
但这样还是不好算,但是题中已经给我们提示了,我们是不断向后添加字符的,每次会新添加一个,算出增量即可。
每次的增量:
。
表示从(集合中只包含的点)到根()的这条链上,所有的串都要贡献,我们求个和。
是上一次的增量,因为我们向后添加了一个字符,所以之前所有的串的贡献都要。
树剖/维护即可。
code:
#include<bits/stdc++.h>
using namespace std;
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
const int maxn=2e5+10;
const int mod=1e9+7;
int n,ans;
int endpos[maxn];
char s[maxn];
inline int read()
{
char c=getchar();int res=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')res=res*10+c-'0',c=getchar();
return res*f;
}
inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
struct SAM
{
int last,tot;
int fa[maxn],len[maxn];
int ch[maxn][26];
SAM(){last=tot=1;}
inline void add(int c)
{
int now=++tot;len[now]=len[last]+1;
int p=last;last=now;
while(p&&!ch[p][c])ch[p][c]=now,p=fa[p];
if(!p){fa[now]=1;return;}
int q=ch[p][c];
if(len[q]==len[p]+1)fa[now]=q;
else
{
int nowq=++tot;len[nowq]=len[p]+1;
memcpy(ch[nowq],ch[q],sizeof(ch[q]));
fa[nowq]=fa[q],fa[q]=fa[now]=nowq;
while(p&&ch[p][c]==q)ch[p][c]=nowq,p=fa[p];
}
}
}sam;
int cnt_edge;
int head[maxn];
struct edge{int to,nxt;}e[maxn<<1];
inline void add_edge(int u,int v)
{
e[++cnt_edge]=(edge){v,head[u]};
head[u]=cnt_edge;
}
int size[maxn],son[maxn],dep[maxn],pre[maxn];
void dfs1(int x,int fa)
{
size[x]=1;
dep[x]=dep[pre[x]=fa]+1;
for(int i=head[x];i;i=e[i].nxt)
{
int y=e[i].to;
if(y==fa)continue;
dfs1(y,x);size[x]+=size[y];
if(size[y]>size[son[x]])son[x]=y;
}
}
int tim;
int dfn[maxn],pos[maxn],top[maxn];
void dfs2(int x,int tp)
{
pos[dfn[x]=++tim]=x;
top[x]=tp;
if(son[x])dfs2(son[x],tp);
for(int i=head[x];i;i=e[i].nxt)
{
int y=e[i].to;
if(y==son[x]||y==pre[x])continue;
dfs2(y,y);
}
}
struct Segment_tree
{
#define len(p) (seg[p].len)
#define sum(p) (seg[p].sum)
#define tag(p) (seg[p].tag)
int len,sum,tag;
}seg[maxn<<2];
void build(int p,int l,int r)
{
if(l==r){len(p)=sam.len[pos[l]]-sam.len[sam.fa[pos[l]]];return;}
int mid=(l+r)>>1;
build(ls(p),l,mid);build(rs(p),mid+1,r);
len(p)=add(len(ls(p)),len(rs(p)));
}
inline void down(int p)
{
if(!tag(p))return;
int k=tag(p);
sum(ls(p))=add(sum(ls(p)),1ll*k*len(ls(p))%mod);
sum(rs(p))=add(sum(rs(p)),1ll*k*len(rs(p))%mod);
tag(ls(p))=add(tag(ls(p)),k),tag(rs(p))=add(tag(rs(p)),k);
tag(p)=0;
}
void change(int p,int l,int r,int ql,int qr)
{
if(l>=ql&&r<=qr)
{
sum(p)=add(sum(p),len(p));
tag(p)++;
return;
}
down(p);
int mid=(l+r)>>1;
if(ql<=mid)change(ls(p),l,mid,ql,qr);
if(qr>mid)change(rs(p),mid+1,r,ql,qr);
sum(p)=add(sum(ls(p)),sum(rs(p)));
}
int query(int p,int l,int r,int ql,int qr)
{
if(l>=ql&&r<=qr)return sum(p);
down(p);
int mid=(l+r)>>1,res=0;
if(ql<=mid)res=add(res,query(ls(p),l,mid,ql,qr));
if(qr>mid)res=add(res,query(rs(p),mid+1,r,ql,qr));
return res;
}
inline void change(int x)
{
while(top[x]!=1)change(1,1,sam.tot,dfn[top[x]],dfn[x]),x=pre[top[x]];
change(1,1,sam.tot,dfn[1],dfn[x]);
}
inline int query(int x)
{
int res=0;
while(top[x]!=1)res=add(res,query(1,1,sam.tot,dfn[top[x]],dfn[x])),x=pre[top[x]];
res=add(res,query(1,1,sam.tot,dfn[1],dfn[x]));
return res;
}
int main()
{
n=read();
scanf("%s",s+1);
for(int i=1;i<=n;i++)sam.add(s[i]-'a'),endpos[i]=sam.last;
for(int i=1;i<=sam.tot;i++)add_edge(sam.fa[i],i),add_edge(i,sam.fa[i]);
dfs1(1,0),dfs2(1,1);
build(1,1,sam.tot);
int sum=0;
for(int i=1;i<=n;i++)
{
sum=add(sum,query(endpos[i]));
change(endpos[i]);
ans=add(ans,sum);
printf("%d\n",ans);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现