题目大意

\(n\)个点的树,每条边上有一个小写字母。

操作:给定2个点\(u\),\(v\)(\(u\)可能等于\(v\))和一个非空字符串\(s\),问从\(u\)到\(v\)的简单路径上的所有边按照到\(u\)的距离从小到大的顺序排列后,边上的字符依次拼接形成的字符串中给定的串\(s\)出现了多少次。

\(n,m\leq 10^5,\sum|s|<=3\times 10^5\)

题解

离线

当\(u->v\)路径上的字符组成的串的子串匹配上询问串时,记\(x\)为\(u,v\)的lca,则有三种可能:1.该子串在路径\(u->x\)上;2.该子串经过点\(x\);3.该子串在路径\(x->v\)上。

第二种情况可以暴力kmp。因为每个询问拿出来暴力kmp的链的长度不超过询问串长度的两倍,所以暴力kmp的总时间复杂度是\(\Theta(\sum |s|)\)

考虑把情况一拆成\(根->u\)减去\(根->x\)。

问题转化为如何在比较短的总时间内处理一些“根到某点的路径上出现了多少个询问串”的询问。

在普通的匹配问题中,在AC自动机上每走过一个点,这个点的fail树祖先中关键点的贡献都+1。也就是说,AC自动机上一个点(对应询问串)的贡献(对应在大串上与之匹配的串的个数)为它fail树后代中被走过的点的个数。对fail树按DFS序剖分,可以用树状数组记录一个点子树中被走过的点。

这题可以将询问离线,将询问串建AC自动机,将“根到某点\(k\)的路径上出现了多少个询问串”的询问放在\(k\)上。

树上DFS的同时在AC自动机上匹配,回溯时也要在AC自动机上消去贡献。每走到一个点,在AC自动机上计算放在这个点的询问的答案。

总时间复杂度\(\Theta((n+m)log \space (\sum|s|))\)。

情况三同理。

代码

#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#define rep(i,x,y) for(register int i=(x);i<=(y);++i)
#define dwn(i,x,y) for(register int i=(x);i>=(y);--i)
#define re register
#define maxn 100010
#define maxm 200010
#define maxl 300010
#define cx(c) (c-'a')
#define xc(x) (x+'a')
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(isdigit(ch)==0 && ch!='-')ch=getchar();
if(ch=='-')f=-1,ch=getchar();
while(isdigit(ch))x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
inline void write(int x)
{
int f=0;char ch[20];
if(!x){puts("0");return;}
if(x<0){putchar('-');x=-x;}
while(x)ch[++f]=x%10+'0',x/=10;
while(f)putchar(ch[f--]);
putchar('\n');
}
int v[maxm],nxt[maxm],w[maxm],fir[maxn],tofa[maxn],cnte,kmps[maxl],kmpS[maxn];
int mk[maxm][2],lca[maxm],ans[maxm],anc[maxn][18],dep[maxn],kmpf[maxl];
int n,q;
typedef struct que{int fh,nd,id,tp;}qs;
qs makeq(int a1,int a2,int a3,int a4){qs tmp;tmp.fh=a1,tmp.nd=a2,tmp.id=a3,tmp.tp=a4;return tmp;}
vector<qs>Q[maxn];
void ade(int u1,int v1,int w1){v[cnte]=v1,w[cnte]=w1,nxt[cnte]=fir[u1],fir[u1]=cnte++;}
struct AC
{
int ch[maxl][26],tr[maxl],dfn[maxl],cntnd,fa[maxl];
int fir[maxl],nxt[maxl],v[maxl],cnte,tim,siz[maxl];
char s[maxl];
queue<int>q;
void ade(int u1,int v1){v[cnte]=v1,nxt[cnte]=fir[u1],fir[u1]=cnte++;}
int extend(int len)
{
int u=0;
rep(i,1,len)
{
if(!ch[u][cx(s[i])])ch[u][cx(s[i])]=++cntnd;
u=ch[u][cx(s[i])];
}
return u;
}
void getfa()
{
rep(i,0,25)if(ch[0][i])q.push(ch[0][i]),fa[ch[0][i]]=0;
while(!q.empty())
{
int u=q.front();q.pop();
rep(i,0,25)
{
if(!ch[u][i])ch[u][i]=ch[fa[u]][i];
else fa[ch[u][i]]=ch[fa[u]][i],q.push(ch[u][i]);
}
}
rep(i,1,cntnd)ade(fa[i],i);
}
void getdf(int u){dfn[u]=++tim;siz[u]=1;for(int k=fir[u];k!=-1;k=nxt[k])getdf(v[k]),siz[u]+=siz[v[k]];}
inline int lt(int x){return x&(-x);}
void add(int x,int k){for(;x<=tim;x+=lt(x))tr[x]+=k;}
int ask(int x){int k=0;for(;x;x-=lt(x))k+=tr[x];return k;}
int query(int u){return ask(dfn[u]+siz[u]-1)-ask(dfn[u]-1);} }t[2];
void getf(int u)
{
rep(i,1,17)anc[u][i]=anc[anc[u][i-1]][i-1];
for(int k=fir[u];k!=-1;k=nxt[k])
{
if(v[k]!=anc[u][0])
{
anc[v[k]][0]=u,dep[v[k]]=dep[u]+1,tofa[v[k]]=w[k];
getf(v[k]);
}
}
}
int Lca(int x,int y)
{
if(dep[y]>dep[x])swap(x,y);
dwn(i,17,0)if(dep[anc[x][i]]>=dep[y]&&anc[x][i])x=anc[x][i];
if(x==y)return x;
dwn(i,17,0)if(anc[x][i]!=anc[y][i])x=anc[x][i],y=anc[y][i];
return anc[x][0];
}
int getkmp(int id,int len,int nd0,int nd1)
{
int Len=min(dep[mk[id][0]]-dep[lca[id]],len-1)+min(dep[mk[id][1]]-dep[lca[id]],len-1);
int x[2],hd=0,tl=Len,nds[2];nds[0]=nd0,nds[1]=nd1;
rep(i,0,1){x[i]=mk[id][i];dwn(j,17,0)if(dep[anc[x[i]][j]]-dep[lca[id]]>=len-1&&anc[x[i]][j])x[i]=anc[x[i]][j];}
rep(i,0,1)if(dep[mk[id][i]]-dep[lca[id]]>=len)Q[x[i]].push_back(makeq(-1,nds[i],id,i));
while(x[0]!=lca[id])kmpS[tl--]=tofa[x[0]],x[0]=anc[x[0]][0];
while(x[1]!=lca[id])kmpS[++hd]=tofa[x[1]],x[1]=anc[x[1]][0];
return Len;
}
int fa[maxn];
int kmp(int len,int Len)
{
fa[1]=0;int hd=0;
if(len>Len){rep(i,1,len)kmps[i]=0;return 0;}
rep(i,1,len-1)
{
while(hd&&kmps[i+1]!=kmps[hd+1])hd=fa[hd];
if(kmps[i+1]!=kmps[hd+1])fa[i+1]=hd;
else fa[i+1]=++hd;
}
int u=0,res=0;
rep(i,1,Len)
{
while(kmps[u+1]!=kmpS[i]&&u)u=fa[u];
if(kmps[u+1]!=kmpS[i])u=0;
else u++;
res+=(u==len);
}
rep(i,1,len)kmps[i]=fa[i]=0;
return res;
}
void getans(int u,int nd0,int nd1)
{
int nd[2],lim=Q[u].size();nd[0]=nd0,nd[1]=nd1;
rep(i,0,lim-1)ans[Q[u][i].id]+=Q[u][i].fh* t[Q[u][i].tp].query(Q[u][i].nd);
for(int k=fir[u];k!=-1;k=nxt[k])
{
if(v[k]!=anc[u][0])
{
rep(i,0,1)t[i].add(t[i].dfn[t[i].ch[nd[i]][w[k]]],1);
getans(v[k],t[0].ch[nd0][w[k]],t[1].ch[nd1][w[k]]);
rep(i,0,1)t[i].add(t[i].dfn[t[i].ch[nd[i]][w[k]]],-1);
}
}
}
int main()
{
n=read(),q=read();
memset(fir,-1,sizeof(fir));
rep(i,0,1)memset(t[i].fir,-1,sizeof(t[i].fir));
rep(i,1,n-1)
{
int x=read(),y=read();char c=getchar();
ade(x,y,cx(c)),ade(y,x,cx(c));
}
getf(1);
rep(i,1,q)
{
mk[i][1]=read(),mk[i][0]=read();scanf("%s",t[0].s+1);
if(mk[i][1]==mk[i][0]){ans[i]=0;continue;}
lca[i]=Lca(mk[i][0],mk[i][1]);
int len=strlen(t[0].s+1),nd[2];
rep(j,1,len)t[1].s[j]=t[0].s[len-j+1],kmps[j]=cx(t[0].s[j]);
rep(j,0,1){if(dep[mk[i][j]]-dep[lca[i]]>=len)nd[j]=t[j].extend(len),Q[mk[i][j]].push_back(makeq(1,nd[j],i,j));}
ans[i]=kmp(len,getkmp(i,len,nd[0],nd[1]));
}
rep(i,0,1)t[i].getfa(),t[i].getdf(0);
getans(1,0,0);
rep(i,1,q)write(ans[i]);
return 0;
}
在线

很遗憾,并不对劲的人在还有资源的时候太菜了。

一些感想

(想到)在线做法(的人)太强了!

并不对劲的bzoj4231: 回忆树的更多相关文章

  1. BZOJ4231 : 回忆树

    一个长度为$|S|$的串在树上匹配有两种情况: 1.在LCA处转弯,那么这种情况只有$O(|S|)$次,暴力提取出长度为$2|S|$的链进行KMP即可. 2.不转弯,那么可以拆成两个到根路径的询问. ...

  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. HDU 1540 Tunnel Warfare (线段树)

    Tunnel Warfare Problem Description During the War of Resistance Against Japan, tunnel warfare was ca ...

  7. bzoj AC倒序

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

  8. 并不对劲的字符串专题(三):Trie树

    据说这些并不对劲的内容是<信息学奥赛一本通提高篇>的配套练习. 并不会讲Trie树. 1.poj1056-> 模板题. 2.bzoj1212-> 设dp[i]表示T长度为i的前 ...

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

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

随机推荐

  1. 1.ibatis核心类

  2. Android跨进程通信广播(Broadcast)

    广播是一种被动跨进程通讯的方式.当某个程序向系统发送广播时,其他的应用程序只能被动地接收广播数据.这就象电台进行广播一样,听众只能被动地收听,而不能主动与电台进行沟通,在应用程序中发送广播比较简单.只 ...

  3. 对于ssh服务的简单配置,似的自己的服务器更加安全

    对于一台服务器,最大的问题莫过于安全.没有安全性的服务器即使再牛*,性能再好,作用再大,也是分分钟被人搞定,而且还是揉虐性的...当然万事没有绝对的安全,我们只是将危险降低而已.本文只针对于ssh服务 ...

  4. springboot2.0+redis实现消息队列+redis做缓存+mysql

    本博客仅供参考,本人实现没有问题. 1.环境 先安装redis.mysql 2.springboot2.0的项目搭建(请自行完成),本人是maven项目,因此只需配置,获取相应的jar包,配置贴出. ...

  5. 怎么用php实现短信验证码发送

    我在在众多的第三方短信服务商里选择了云片网这个短信服务商,我也会尽可能利用最简单的方式去帮助广大开发者解决短信验证码功能模块的实现. 再次之前我也参考了大部分网上的博客等,大多数都是把云片网的demo ...

  6. docker数据管理(2)

    一.docker存储资源类型 docker两种存储资源类型 用户在使用 Docker 的过程中,势必需要查看容器内应用产生的数据,或者需要将容器内数据进行备份,甚至多个容器之间进行数据共享,这必然会涉 ...

  7. Xib设置label自动换行和Label的顶部对齐

    真的是不想说自己了,一个Xib纠结了一天,简直了,整整被虐了一上午啊...... 不知道这是Xcode8的问题呢....还是我的Xib约束什么的问题..... 只想说的是,以前也是这么设置的,明明可以 ...

  8. 转:获取windows凭证管理器明文密码

    1.运行cmdkey /list查看windows保存凭证 方法1.mimikaz mimikatz vault::cred 2.利用powershell尝试获取 windows 普通凭据类型中的明文 ...

  9. JSX AS DSL? 写个 Mock API 服务器看看

    这几天打算写一个简单的 API Mock 服务器,老生常谈哈?其实我是想讲 JSX, Mock 服务器只是一个幌子. 我在寻找一种更简洁.方便.同时又可以灵活扩展的.和别人不太一样的方式,来定义各种 ...

  10. Windows 桌面利用 Navicat等工具连接 虚拟机Mysql

    1.mysql>use mysql;2.mysql>update user set host = '%' where user ='root';3.mysql>select host ...