这里给出一个后缀自动机的做法.
假设每次询问 $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. Linux 概念与快捷方式

    概念 何为shell Shell 是指"提供给使用者使用界面"的软件(命令解析器),类似于 DOS 下的 command(命令行)和后来的 cmd.exe .普通意义上的 Shel ...

  2. 设计模式:解释器模式(Interpreter)

    为人处事是一门大学问,察言观色.听懂弦外之音都是非常重要的,老板跟你说“XX你最近表现平平啊,还得要多努力”,如果你不当回事,平常对待,可能下次就是“XX,恩,你人还是不错,平常工作也很努力,但是我想 ...

  3. 用Java实现对英文版《飘》的文件读取与写入操作

    从文件读入<飘>的英文版,并将结果输出到文件中 要求一: 实现对英文版<飘>的字母出现次数统计 package File; import java.io.FileInputSt ...

  4. idea 新建maven项目时,避免每次都需要指定自己的maven目录

    01 .File->Other Settings -> Settings for New Project 02. 将Maven home directory目录修改成我们自己安装Maven ...

  5. 这38个小技巧告诉你如何快速学习MySQL数据库2

    1.如何快速掌握MySQL? ⑴培养兴趣兴趣是最好的老师,不论学习什么知识,兴趣都可以极大地提高学习效率.当然学习MySQL 5.6也不例外.⑵夯实基础计算机领域的技术非常强调基础,刚开始学习可能还认 ...

  6. LintCode 64---合并排序数组

    public class Solution { /* * @param A: sorted integer array A which has m elements, but size of A is ...

  7. Spring的基本应用(1):依赖以及控制反转

    在说到这里的时候,首先要说下程序的耦合和解耦,以便对上节做一个解释. 一.程序的耦合和解耦 1.程序的耦合性(Copling) (1)程序的耦合性,也叫做耦合度,是对模块之间关联程度的度量,耦合性的强 ...

  8. Linux Exploit系列之六 绕过ASLR - 第一部分

    绕过ASLR - 第一部分 什么是 ASLR? 地址空间布局随机化(ASLR)是随机化的利用缓解技术: 堆栈地址 堆地址 共享库地址 一旦上述地址被随机化,特别是当共享库地址被随机化时,我们采取的绕过 ...

  9. window.location.href 与 window.location.href 的区别

  10. busybox介绍

    BusyBox 是一个集成了一百多个最常用linux命令和工具的软件.BusyBox 将许多具有共性的小版本的UNIX工具结合到一个单一的可执行文件.这样的集合可以替代大部分常用工具比如的GNU fi ...