这里给出一个后缀自动机的做法.
假设每次询问 $t$ 在所有 $s$ 中的出现次数,那么这是非常简单的:
直接对 $s$ 构建后缀自动机,随便维护一下 $endpos$ 大小就可以.
然而,想求 $t$ 在 $trie$ 树中一个节点到根的字符串中的出现次数就难了很多.
我们慢慢讲:
首先,我们对题中给的 $trie$ 树(即所有 $s$ 串)构建广义后缀自动机.
因为后缀自动机能识别所有的子串,所以可以直接将 $t$ 在自动机上匹配.
假设匹配成功,即得到终止节点 $p$.
那么我们想求 $s_{i}$ 中包含 $p$ 的个数.
令 $s_{i}$ 对应到 $trie$ 树的终止节点为 $end(i)$ ,那么将 $end(i)$ 到根节点的所有字符都插入后缀自动机之后,对答案有贡献的就是 $trie$ 中 $end(i)$ 到根中后缀包含 $t$ 的节点个数.
而巧妙的是,对 $s_{i}$ 有贡献的所有 $trie$ 中的节点之间的位置关系是链的关系.
即他们都在 $end(i)$ 到根节点这条路径上.
于是,我们就联想,什么数据结构,能让深度递增的节点编号连续呢?
——树链剖分.
没错,我们对 $trie$ 来一遍树剖求解每个点的树剖序.
对后缀自动机每一个节点建一个线段树,维护的是后缀树中该节点为根子树中所有树剖序的所有标号种类.
对于这个,我们可以在扩展 $trie$ 树字符的时候就在自动机中新建节点插入该点的树剖序,然后扩展完 $trie$ 树后再来一遍线段树合并.
最后,只需暴力跳重链,并查询 $dfn[top[x]]$~$dfn[x]$ 在 $p$ 节点所在线段树中有几个出现.
时间复杂度为 $O(nlog^2n)$,常数巨大,远没有 AC 自动机好写,运行快.
不过,这确实是一道练习后缀自动机的好题.

#include <map>
#include <vector>
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 400003
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
char S[N];
int n,m,rt[N<<1];
namespace Trie {
int id[N],tim;
struct Node {
map<int,int>ch;
int siz,dfn,top,son,fa;
}t[N];
void dfs1(int u) {
t[u].siz=1;
for(int i=0;i<27;++i)
if(t[u].ch.count(i)) {
int v=t[u].ch[i];
t[v].fa=u,dfs1(v),t[u].siz+=t[v].siz;
if(!t[u].son||t[v].siz>t[t[u].son].siz) t[u].son=v;
}
}
void dfs2(int u,int tp) {
t[u].top=tp;
t[u].dfn=++tim;
if(t[u].son) dfs2(t[u].son,tp);
for(int i=0;i<27;++i)
if(t[u].ch.count(i)) {
int v=t[u].ch[i];
if(v!=t[u].son)
dfs2(v,v);
}
}
void build_tree() {
dfs1(0), dfs2(0,0);
}
};
namespace seg {
#define ls t[x].lson
#define rs t[x].rson
int tot;
struct Node {
int lson,rson,sum;
}t[N*50];
void pushup(int x) {
t[x].sum=t[ls].sum+t[rs].sum;
}
void update(int &x,int l,int r,int p,int v) {
if(!x)
x=++tot;
if(l==r) {
t[x].sum=v;
return;
}
int mid=(l+r)>>1;
if(p<=mid) update(ls,l,mid,p,v);
else update(rs,mid+1,r,p,v);
pushup(x);
}
int query(int x,int l,int r,int L,int R) {
if(!x) return 0;
if(l>=L&&r<=R) return t[x].sum;
int mid=(l+r)>>1,re=0;
if(L<=mid) re+=query(ls,l,mid,L,R);
if(R>mid) re+=query(rs,mid+1,r,L,R);
return re;
}
int merge(int l,int r,int x,int y,int tt) {
if(!x||!y)
return x+y;
int oo=++tot;
if(l==r)
t[oo].sum=1;
else {
int mid=(l+r)>>1;
t[oo].lson=merge(l,mid,t[x].lson,t[y].lson,tt);
t[oo].rson=merge(mid+1,r,t[x].rson,t[y].rson,tt);
pushup(oo);
}
return oo;
}
#undef ls
#undef rs
};
namespace SAM {
int tot,id[N<<1],tax[N<<1],rk[N<<1];
struct Node {
map<int,int>ch;
int len,pre;
}t[N<<1];
struct Edge {
int from,c;
Edge(int from=0,int c=0):from(from),c(c){}
};
queue<int>q;
int extend(int lst,int c,int i) {
int p=lst;
if(t[p].ch.count(c)) {
int q=t[p].ch[c];
if(t[q].len==t[p].len+1) seg::update(rt[q],1,Trie::tim,i,1),lst=q;
else {
int nq=++tot;
t[nq].len=t[p].len+1;
t[nq].pre=t[q].pre,t[q].pre=nq;
t[nq].ch=t[q].ch;
seg::update(rt[nq],1,Trie::tim,i,1);
for(;p&&t[p].ch.count(c)&&t[p].ch[c]==q;p=t[p].pre) t[p].ch[c]=nq;
lst=nq;
}
}
else {
int np=++tot;
t[np].len=t[p].len+1;
seg::update(rt[np],1,Trie::tim,i,1);
for(;p&&!t[p].ch.count(c);p=t[p].pre) t[p].ch[c]=np;
if(!p) t[np].pre=1;
else {
int q=t[p].ch[c];
if(t[q].len==t[p].len+1) t[np].pre=q;
else {
int nq=++tot;
t[nq].len=t[p].len+1;
t[nq].pre=t[q].pre,t[q].pre=t[np].pre=nq;
t[nq].ch=t[q].ch;
for(;p&&t[p].ch.count(c)&&t[p].ch[c]==q;p=t[p].pre) t[p].ch[c]=nq;
}
}
lst=np;
}
return lst;
}
void construct() {
int i,j;
id[0]=tot=1;
q.push(0);
while(!q.empty()) {
int u=q.front();q.pop();
for(i=0;i<27;++i) {
if(Trie::t[u].ch.count(i)) {
int v=Trie::t[u].ch[i];
q.push(v);
id[v]=extend(id[u],i,Trie::t[v].dfn);
}
}
}
for(i=1;i<=tot;++i) tax[i]=0;
for(i=1;i<=tot;++i) ++tax[t[i].len];
for(i=1;i<=tot;++i) tax[i]+=tax[i-1];
for(i=1;i<=tot;++i) rk[tax[t[i].len]--]=i;
for(i=tot;i>1;--i) {
int u=rk[i];
int ff=t[u].pre;
if(ff>1)
rt[ff]=seg::merge(1,Trie::tim,rt[u],rt[ff],0);
}
}
};
void solve(int x) {
int p=1,i,len=strlen(S+1),re=0;
for(i=1;i<=len;++i) {
int c=S[i]-'a';
if(SAM::t[p].ch.count(c))
p=SAM::t[p].ch[c];
else {
printf("0\n");
return;
}
}
for(x=Trie::id[x];x;x=Trie::t[Trie::t[x].top].fa) {
re+=seg::query(rt[p],1,Trie::tim,Trie::t[Trie::t[x].top].dfn,Trie::t[x].dfn);
}
printf("%d\n",re);
}
int main() {
int i,j;
// setIO("input");
scanf("%d",&n);
for(i=1;i<=n;++i) {
int op,lst=0;
char str[3];
scanf("%d",&op);
if(op==2) scanf("%d",&lst),lst=Trie::id[lst];
scanf("%s",str);
if(!Trie::t[lst].ch.count(str[0]-'a')) {
Trie::t[lst].ch[str[0]-'a']=i;
Trie::id[i]=i;
}
else Trie::id[i]=Trie::t[lst].ch[str[0]-'a'];
}
Trie::build_tree();
SAM::construct();
scanf("%d",&m);
for(i=1;i<=m;++i)
scanf("%d%s",&j,S+1), solve(j);
return 0;
}

  

CF G. Indie Album 广义后缀自动机+树链剖分+线段树合并的更多相关文章

  1. 【bzoj5210】最大连通子块和 树链剖分+线段树+可删除堆维护树形动态dp

    题目描述 给出一棵n个点.以1为根的有根树,点有点权.要求支持如下两种操作: M x y:将点x的点权改为y: Q x:求以x为根的子树的最大连通子块和. 其中,一棵子树的最大连通子块和指的是:该子树 ...

  2. Aizu 2450 Do use segment tree 树链剖分+线段树

    Do use segment tree Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://www.bnuoj.com/v3/problem_show ...

  3. HDU 2460 Network(双连通+树链剖分+线段树)

    HDU 2460 Network 题目链接 题意:给定一个无向图,问每次增加一条边,问个图中还剩多少桥 思路:先双连通缩点,然后形成一棵树,每次增加一条边,相当于询问这两点路径上有多少条边,这个用树链 ...

  4. ACM-ICPC 2018 焦作赛区网络预赛 E Jiu Yuan Wants to Eat (树链剖分+线段树)

    题目链接:https://nanti.jisuanke.com/t/31714 题意:给你一棵树,初始全为0,有四种操作: 1.u-v乘x    2.u-v加x   3. u-v取反  4.询问u-v ...

  5. POJ3237 Tree 树链剖分 线段树

    欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目传送门 - POJ3237 题意概括 Description 给你由N个结点组成的树.树的节点被编号为1到N,边被编号为1 ...

  6. 【bzoj4712】洪水 树链剖分+线段树维护树形动态dp

    题目描述 给出一棵树,点有点权.多次增加某个点的点权,并在某一棵子树中询问:选出若干个节点,使得每个叶子节点到根节点的路径上至少有一个节点被选择,求选出的点的点权和的最小值. 输入 输入文件第一行包含 ...

  7. 2243: [SDOI2011]染色 树链剖分+线段树染色

    给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段), 如“112221”由3段组 ...

  8. 【Codeforces827D/CF827D】Best Edge Weight(最小生成树性质+倍增/树链剖分+线段树)

    题目 Codeforces827D 分析 倍增神题--(感谢T*C神犇给我讲qwq) 这道题需要考虑最小生成树的性质.首先随便求出一棵最小生成树,把树边和非树边分开处理. 首先,对于非树边\((u,v ...

  9. 洛谷P3313 [SDOI2014]旅行 题解 树链剖分+线段树动态开点

    题目链接:https://www.luogu.org/problem/P3313 这道题目就是树链剖分+线段树动态开点. 然后做这道题目之前我们先来看一道不考虑树链剖分之后完全相同的线段树动态开点的题 ...

随机推荐

  1. [c++] SYSTEM_INFO

    SYSTEM_INFO,Win32 API函数GetSystemInfo所使用的结构体. 说明 SYSTEM_INFO结构体包含了当前计算机的信息.这个信息包括计算机的体系结构.中央处理器的类型.系统 ...

  2. [转帖]微软 SQL Server 2008/R2 停止支持

    微软 SQL Server 2008/R2 停止支持 微软停止支持 SQLSERVER 2008R2 https://t.cj.sina.com.cn/articles/view/3172142827 ...

  3. 【Vue高级知识】细谈Vue 中三要素(响应式+模板+render函数)

    [Vue高级知识]细谈Vue 中三要素(响应式+模板+render函数):https://blog.csdn.net/m0_37981569/article/details/93304809

  4. GDOI2018游记

    前言 不知怎的,本蒟蒻居然拿到了GDOI参赛名额 于是乎,我稀里糊涂地跟着诸位大佬屁颠屁颠地来到了阔别已久的中山一中 腐败difficult and interesting的GDOI比赛就这样开始了. ...

  5. CALL apoc.cypher.doIt创建动态节点的时候怎么指定多个标签?

    下面的创建节点实例,请教一下CALL apoc.cypher.doIt如何创建多个标签?现在的方式是只能指定一个标签! UNWIND [{name:"sdasdsad234fdgsasdfa ...

  6. UI测试

    先是从一张图开始,让大家看看这个图里有什么不妥: 接着告诉大家具体有哪些不妥: 然后结合这个找茬的过程分享下界面测试的概念和方法. 界面测试:简称UI测试,测试功能模块界面上看到的所有元素(包括空文字 ...

  7. git push 到 github

    今天来简单整理一下,如何利用git命令把代码提交到GitHub平台上去,当然要提交代码到GitHub上去,您首先得要有GitHub账号,账号如何申请这里就不多做解释了 第一步:先到官网下载git安装包 ...

  8. poj 2033 Alphacode (dp)

    Alphacode Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 13378   Accepted: 4026 Descri ...

  9. Jansson库的使用简介

    一.Jansson的安装: 二.jansson相关的API: https://jansson.readthedocs.io/en/latest/apiref.html#c.json_t string ...

  10. linux下创建软链--laravel软链

    ln -s /www/wwwroot/project_name/storage/app/public/ /www/wwwroot/project_name/public/storage