BZOJ4231 : 回忆树
一个长度为$|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 : 回忆树的更多相关文章
- 并不对劲的bzoj4231: 回忆树
题目大意 \(n\)个点的树,每条边上有一个小写字母. 操作:给定2个点\(u\),\(v\)(\(u\)可能等于\(v\))和一个非空字符串\(s\),问从\(u\)到\(v\)的简单路径上的所有边 ...
- 【BZOJ4231】回忆树 离线+fail树+KMP
[BZOJ4231]回忆树 Description 回忆树是树. 具体来说,是n个点n-1条边的无向连通图,点标号为1~n,每条边上有一个字符(出于简化目的,我们认为只有小写字母). 对一棵回忆树来说 ...
- 「模拟赛20180306」回忆树 memory LCA+KMP+AC自动机+树状数组
题目描述 回忆树是一棵树,树边上有小写字母. 一次回忆是这样的:你想起过往,触及心底--唔,不对,我们要说题目. 这题中我们认为回忆是这样的:给定 \(2\) 个点 \(u,v\) (\(u\) 可能 ...
- 【bzoj4231】回忆树
题解: 树上的串匹配,模式串的总长$|S|$,令$\overline {S} $为$S$的反串: 对$S$和$\overline {S} $分别建自动机 $u -> v$可以分成三个部分去统计 ...
- 哈夫曼(huffman)树和哈夫曼编码
哈夫曼树 哈夫曼树也叫最优二叉树(哈夫曼树) 问题:什么是哈夫曼树? 例:将学生的百分制成绩转换为五分制成绩:≥90 分: A,80-89分: B,70-79分: C,60-69分: D,<60 ...
- bzoj AC倒序
Search GO 说明:输入题号直接进入相应题目,如需搜索含数字的题目,请在关键词前加单引号 Problem ID Title Source AC Submit Y 1000 A+B Problem ...
- LOJ#510 北校门外的回忆(找性质+倍增+线段树)
这题一场模拟赛我们出了弱化版(n<=1e6),抄题面给的程序能拿到71分的好成绩 其实后面的29分是加了几个1e9的数据卡人 这糟老头子真是坏得很 正解我们机房看了三天 在这里感谢这篇题解的作者 ...
- LOJ#510. 「LibreOJ NOI Round #1」北校门外的回忆(线段树)
题面 传送门 题解 感谢\(@M\_sea\)的代码我总算看懂题解了-- 这个操作的本质就是每次把\(x\)的\(k\)进制最低位乘\(2\)并进位,根据基本同余芝士如果\(k\)是奇数那么最低位永远 ...
- LOJ 北校门外的回忆 倍增+线段树
正解:倍增+线段树 解题报告: 传送门! $umm$这题有个对正解毫无启发的部分分还有个正解,都挺神仙的所以我都写了趴$QAQ$ 先说部分分 可以考虑把$x$向$x+lowbit(x)$连边,然后当$ ...
随机推荐
- struts2 javaweb 过滤器、监听器 拦截器 原理
转: 过滤器.监听器 拦截器 过滤器 创建一个 Filter 只需两个步骤: (1)创建 Filter 处理类: (2)在 web.xml 文件中配置 Filter . 创建 Filter 必须实现 ...
- Scrapy爬取美女图片 (原创)
有半个月没有更新了,最近确实有点忙.先是华为的比赛,接着实验室又有项目,然后又学习了一些新的知识,所以没有更新文章.为了表达我的歉意,我给大家来一波福利... 今天咱们说的是爬虫框架.之前我使用pyt ...
- ViewPager部分源码分析三:scroll
手指在屏幕上滑动,触发到onTouchEvent(),执行case MotionEvent.ACTION_MOVE: ... public boolean onTouchEvent(MotionEve ...
- 关于Mesos和Kubernetes的区别
这个主题应该和服务发现注册一样,进入视野...
- jQuery函数attr()和prop()的区别
在jQuery中,attr()函数和prop()函数都用于设置或获取指定的属性,它们的参数和用法也几乎完全相同. 但不得不说的是,这两个函数的用处却并不相同.下面我们来详细介绍这两个函数之间的区别. ...
- 浅谈config文件的使用
一.缘起 最近做项目开始使用C#,因为以前一直使用的是C++,因此面向对象思想方面的知识还是比较全面的,反而是因没有经过完整.系统的.Net方面知识的系统学习,经常被一些在C#老鸟眼里几乎是常识的小知 ...
- 攻城狮在路上(叁)Linux(十七)--- linux磁盘与文件管理概述
一.复习知识点: 1.扇区是最小的物理存储单位,大小为512bytes. 2.扇区组成一个圆,成为柱面,柱面是分区的最小单位. 3.第一个扇区很重要,因为包含了MBR(446字节)和分区表(64字节) ...
- Oracle RMAN备份策略
建立增量备份:如果数据库运行于不归档模式下,只能在数据库干净关闭的情况下 ( 以 normal .immediate . transactional 方式关闭 ) 才能进行一致性的增量备份,如果数据库 ...
- Oracle【IT实验室】数据库备份与恢复之五:Flashback
Flashback在开发环境(有时生产环境的特殊情况下)是很有用的一个工具. 5.1 9i Flashback 简介 5.1.1 原理 当数据 update 或 delete ...
- WPF PRISM开发入门一( 初始化PRISM WPF程序)
这篇博客将介绍在WPF项目中引入PRISM框架进行开发的一些基础知识.目前最新的PRISM的版本是Prism 6.1.0,可以在Github上获取PRISM的源码.这个系列的博客将选择PRISM 4. ...