一个长度为$|S|$的串在树上匹配有两种情况:

1.在LCA处转弯,那么这种情况只有$O(|S|)$次,暴力提取出长度为$2|S|$的链进行KMP即可。

2.不转弯,那么可以拆成两个到根路径的询问。

对所有串的正反串建立AC自动机,求出fail树上每个点的DFS序。

然后DFS原树,记录在AC自动机上走到了哪个点,在那个点$+1$,回溯的时候$-1$。

那么一个询问的答案就是fail树上的子树和,树状数组维护即可。

时间复杂度$O(n\log n+|S|)$。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100010,M=600010;
char a[M],b[N];
int n,m,len,i,j,x,y,z,g[N],v[N<<1],w[N<<1],nxt[N<<1],ed,G[N],V[N<<2],W[N<<2],NXT[N<<2];
int size[N],son[N],f[N],fs[N],d[N],top[N],loc[N],seq[N],dfn,ans[N];
inline void add(int x,int y,int z){v[++ed]=y;w[ed]=z;nxt[ed]=g[x];g[x]=ed;}
inline void ADD(int x,int y,int z){V[++ed]=y;W[ed]=z;NXT[ed]=G[x];G[x]=ed;}
void dfs(int x){
size[x]=1;
for(int i=g[x];i;i=nxt[i])if(v[i]!=f[x]){
f[v[i]]=x,fs[v[i]]=w[i],d[v[i]]=d[x]+1;
dfs(v[i]),size[x]+=size[v[i]];
if(size[v[i]]>size[son[x]])son[x]=v[i];
}
}
void dfs2(int x,int y){
seq[loc[x]=++dfn]=x;top[x]=y;
if(son[x])dfs2(son[x],y);
for(int i=g[x];i;i=nxt[i])if(v[i]!=son[x]&&v[i]!=f[x])dfs2(v[i],v[i]);
}
inline int lca(int x,int y){
for(;top[x]!=top[y];x=f[top[x]])if(d[top[x]]<d[top[y]])swap(x,y);
return d[x]<d[y]?x:y;
}
inline int kth(int x,int y){
while(d[x]-d[top[x]]<y)y-=d[x]-d[top[x]]+1,x=f[top[x]];
return seq[loc[x]-y];
}
namespace KMP{
int nxt[M];
inline void cross(int x,int y,int&ret){
int t=0,i,j,A=kth(x,d[x]-min(d[z]+len-1,d[x])),B=kth(y,d[y]-min(d[z]+len-1,d[y]));
for(nxt[0]=j=-1,i=1;i<len;nxt[i++]=j){
while(~j&&a[j+1]!=a[i])j=nxt[j];
if(a[j+1]==a[i])j++;
}
for(j=-1;A!=z;A=f[A]){
while(~j&&a[j+1]!=fs[A])j=nxt[j];
if(a[j+1]==fs[A])j++;
if(j==len-1)ret++,j=nxt[j];
}
while(B!=z)b[++t]=fs[B],B=f[B];
while(t){
while(~j&&a[j+1]!=b[t])j=nxt[j];
if(a[j+1]==b[t])j++;
if(j==len-1)ret++,j=nxt[j];
t--;
}
}
}
namespace AC{
int tot,son[M][26],f[M],q[M],g[M],nxt[M],st[M],en[M],dfn,bit[M];
inline void addedge(int x,int y){nxt[y]=g[x];g[x]=y;}
inline int ins0(){
int x=0;
for(int i=0;i<len;i++){
if(!son[x][a[i]])son[x][a[i]]=++tot;
x=son[x][a[i]];
}
return x;
}
inline int ins1(){
int x=0;
for(int i=len-1;~i;i--){
if(!son[x][a[i]])son[x][a[i]]=++tot;
x=son[x][a[i]];
}
return x;
}
void dfs(int x){
st[x]=++dfn;
for(int i=g[x];i;i=nxt[i])dfs(i);
en[x]=dfn;
}
inline void add(int x,int p){for(x=st[x];x<=dfn;x+=x&-x)bit[x]+=p;}
inline int sum(int x){int t=0;for(;x;x-=x&-x)t+=bit[x];return t;}
inline int ask(int x){return sum(en[x])-sum(st[x]-1);}
void make(){
int h=1,t=0,i,j,x;f[0]=-1;
for(i=0;i<26;i++)if(son[0][i])q[++t]=son[0][i];
while(h<=t)for(x=q[h++],i=0;i<26;i++)
if(son[x][i])f[son[x][i]]=son[f[x]][i],q[++t]=son[x][i];
else son[x][i]=son[f[x]][i];
for(i=1;i<=tot;i++)addedge(f[i],i);
dfs(0);
}
}
void dfs3(int x,int y){
AC::add(y,1);
for(int i=G[x];i;i=NXT[i]){
int o=W[i];
if(o>0)ans[o]+=AC::ask(V[i]);else ans[-o]-=AC::ask(V[i]);
}
for(int i=g[x];i;i=nxt[i])if(v[i]!=f[x])dfs3(v[i],AC::son[y][w[i]]);
AC::add(y,-1);
}
int main(){
scanf("%d%d",&n,&m);
for(i=1;i<n;i++)scanf("%d%d%s",&x,&y,a),add(x,y,a[0]-'a'),add(y,x,a[0]-'a');
dfs(1),dfs2(1,1);
for(ed=0,i=1;i<=m;i++){
scanf("%d%d%s",&x,&y,a);len=strlen(a);z=lca(x,y);
for(j=0;j<len;j++)a[j]-='a';
KMP::cross(x,y,ans[i]);
if(d[x]-d[z]>=len){
j=AC::ins1();
ADD(x,j,i);
ADD(kth(x,d[x]-d[z]-len+1),j,-i);
}
if(d[y]-d[z]>=len){
j=AC::ins0();
ADD(y,j,i);
ADD(kth(y,d[y]-d[z]-len+1),j,-i);
}
}
AC::make();
dfs3(1,0);
for(i=1;i<=m;i++)printf("%d\n",ans[i]);
return 0;
}

  

BZOJ4231 : 回忆树的更多相关文章

  1. 并不对劲的bzoj4231: 回忆树

    题目大意 \(n\)个点的树,每条边上有一个小写字母. 操作:给定2个点\(u\),\(v\)(\(u\)可能等于\(v\))和一个非空字符串\(s\),问从\(u\)到\(v\)的简单路径上的所有边 ...

  2. 【BZOJ4231】回忆树 离线+fail树+KMP

    [BZOJ4231]回忆树 Description 回忆树是树. 具体来说,是n个点n-1条边的无向连通图,点标号为1~n,每条边上有一个字符(出于简化目的,我们认为只有小写字母). 对一棵回忆树来说 ...

  3. 「模拟赛20180306」回忆树 memory LCA+KMP+AC自动机+树状数组

    题目描述 回忆树是一棵树,树边上有小写字母. 一次回忆是这样的:你想起过往,触及心底--唔,不对,我们要说题目. 这题中我们认为回忆是这样的:给定 \(2\) 个点 \(u,v\) (\(u\) 可能 ...

  4. 【bzoj4231】回忆树

    题解: 树上的串匹配,模式串的总长$|S|$,令$\overline {S} $为$S$的反串: 对$S$和$\overline {S} $分别建自动机 $u -> v$可以分成三个部分去统计 ...

  5. 哈夫曼(huffman)树和哈夫曼编码

    哈夫曼树 哈夫曼树也叫最优二叉树(哈夫曼树) 问题:什么是哈夫曼树? 例:将学生的百分制成绩转换为五分制成绩:≥90 分: A,80-89分: B,70-79分: C,60-69分: D,<60 ...

  6. bzoj AC倒序

    Search GO 说明:输入题号直接进入相应题目,如需搜索含数字的题目,请在关键词前加单引号 Problem ID Title Source AC Submit Y 1000 A+B Problem ...

  7. LOJ#510 北校门外的回忆(找性质+倍增+线段树)

    这题一场模拟赛我们出了弱化版(n<=1e6),抄题面给的程序能拿到71分的好成绩 其实后面的29分是加了几个1e9的数据卡人 这糟老头子真是坏得很 正解我们机房看了三天 在这里感谢这篇题解的作者 ...

  8. LOJ#510. 「LibreOJ NOI Round #1」北校门外的回忆(线段树)

    题面 传送门 题解 感谢\(@M\_sea\)的代码我总算看懂题解了-- 这个操作的本质就是每次把\(x\)的\(k\)进制最低位乘\(2\)并进位,根据基本同余芝士如果\(k\)是奇数那么最低位永远 ...

  9. LOJ 北校门外的回忆 倍增+线段树

    正解:倍增+线段树 解题报告: 传送门! $umm$这题有个对正解毫无启发的部分分还有个正解,都挺神仙的所以我都写了趴$QAQ$ 先说部分分 可以考虑把$x$向$x+lowbit(x)$连边,然后当$ ...

随机推荐

  1. JDK、Jmeter、Android环境变量配置

    JDK环境变量 1.在系统变量里点击新建,变量名填写JAVA_HOME,变量值填写JDK的安装路径,在这里就填写"D:\Program Files\Java\jdk1.6.0_26" ...

  2. I-number

    以下是真坑爹题目: 此题必须输出前导零,否则永远a不了 I-number Time Limit: 5000MS Memory limit: 65536K 题目描述 The I-number of x ...

  3. js正则表达式:验证邮箱格式、密码复杂度、手机号码、QQ号码

    直接上代码             Java   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ...

  4. T-SQL 常用语句

    1. 查看 Table 或者 Column 被那些object(存储过程.函数或View)调用. select a.* from sysobjects a, syscomments b where a ...

  5. flume-ng 集群搭脚本

    #!/bin/bash # author: xirong # date : -- ##### 搭建 flume 集群的脚本 # 注意: # . 需要 jdk7 环境,如果没有 Java 环境,请配置 ...

  6. mysql 查询优化规则

    .请不要在SELECT中使用DISTINCT: #会用到临时表 .尽可能不要SELECT *,而应该查询需要用到的指定几个字段: .不要对两个大表进行联合,无论是内联或外联.对于需要对两个或多个表进行 ...

  7. FP-Growth算法及演示程序

    FP-Growth算法 FP-Growth(频繁模式增长)算法是韩家炜老师在2000年提出的关联分析算法,它采取如下分治策略:将提供频繁项集的数据库压缩到一棵频繁模式树(FP-Tree),但仍保留项集 ...

  8. 用.NET开发通用Windows App

    (此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:随着Windows 10的正式发布,作为.NET开发人员应该开始或多或少了解一下通用( ...

  9. linux网络协议

    网络协议 本章节主要介绍linxu网络模型.以及常用的网络协议分析以太网协议.IP协议.TCP协议.UDP协议 一.网络模型 TCP/IP分层模型的四个协议层分别完成以下的功能: 第一层 网络接口层 ...

  10. Javascript实现时钟

    <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <m ...