题目链接

problem

有\(n\)个字符串,对于第\(i\)个字符串通过以下两种方式中的一个给出。

  1. \(1\; c\),该字符串只含一个字符\(c\)。

  2. \(2\ x\ c\),该字符串为第\(x(1\le x < i)\)个字符串末尾添加一个字符\(c\)得到。

有\(m\)次询问,每次询问给出一个字符串\(s\)和位置编号\(x\),问在上述第\(x\)个字符串中,字符串\(s\)出现了几次。

solution

需要用到\(AC\)自动机,树状数组,\(dfs\)序。

首先将询问离线下来,对于所有询问的字符串建立一个\(AC\)自动机,从而求出\(fail\)树。然后利用\(fail\)树的性质:一个字符串在母串中出现的次数为将母串在AC自动机上跑一遍并将走到的位置权值+1,该字符串所对应的\(fail\)节点的子树权值和。

还有一个需要解决的问题,如果将母串在AC自动机上跑,如果暴力跑显然不行。所以我们发现他给出这\(n\) 个字符串的方式也是一棵树的形式。所以我们就可以用以下方式跑。

dfs(u,p) {//u为当前节点,p为其父亲在AC自动机上所跑到的节点
将p移向u在AC自动机上跑到的节点
将p所对应的的节点权值+1
for(v是u的儿子) dfs(v,p)
统计所有对于u这个串的查询的答案。
将p所对应的节点权值-1
}

发现通过上面的方式,就可以保证每次查询的时候只有所查询的字符串在\(AC\)自动机上产生了贡献。

在\(fail\)树上查询子树权值和,可以用\(dfs\)序+树状数组完成。

code

//@Author: wxyww
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
#include<map>
#include<string>
using namespace std;
typedef long long ll;
const int N = 400010;
ll read() {
ll x = 0,f = 1; char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0',c = getchar();}
return x * f;
}
char ss[N],s[N];
int fail[N],trie[N][30];
struct node {
int v,nxt;
}e[N];
int idtot,siz[N],tree[N],fa[N],bh[N],ans[N],head[N],ejs,tot;
void add(int u,int v) {
e[++ejs].v = v;e[ejs].nxt = head[u];head[u] = ejs;
}
vector<pair<int,int> >que[N];
void update(int pos,int c) {
while(pos <= idtot) {
tree[pos] += c;
pos += pos & -pos;
}
}
int query(int pos) {
int ret = 0;
while(pos) {
ret += tree[pos];
pos -= pos & -pos;
}
return ret;
}
int add(char *t) {
int n = strlen(t + 1),p = 0;
for(int i = 1;i <= n;++i) {
if(!trie[p][t[i] - 'a']) trie[p][t[i] - 'a'] = ++tot;
p = trie[p][t[i] - 'a'];
}
// printf("!!!%d\n",p);
return p;
}
queue<int>q;
vector<int>E[N];
void build() {
for(int i = 0;i < 26;++i) if(trie[0][i]) q.push(trie[0][i]); while(!q.empty()) {
int u = q.front();q.pop();
E[fail[u]].push_back(u);
for(int i = 0;i < 26;++i) {
if(!trie[u][i]) trie[u][i] = trie[fail[u]][i];
else fail[trie[u][i]] = trie[fail[u]][i],q.push(trie[u][i]);
}
}
}
void AC_dfs(int u) {
int k = E[u].size();
bh[u] = ++idtot;siz[u] = 1;
for(int i = 0;i < k;++i) {
int v = E[u][i];
AC_dfs(v);
siz[u] += siz[v];
}
}
int getans(int p) {
// printf("!!!%d %d\n",bh[p] + siz[p] - 1,bh[p]);
return query(bh[p] + siz[p] - 1) - query(bh[p] - 1);
}
void dfs(int u,int p) {
p = trie[p][s[u] - 'a'];
// if(u == 2) printf("!!!%d\n",u); update(bh[p],1);
// printf("!!%d %d\n",u,s[u] - 'a');
// printf("!!!%d %d\n",u,p);
for(int i = head[u];i;i = e[i].nxt) dfs(e[i].v,p); int k = que[u].size();
for(int i = 0;i < k;++i) ans[que[u][i].second] = getans(que[u][i].first); update(bh[p],-1); }
int main() {
int n = read();
for(int i = 1;i <= n;++i) {
int opt = read();
if(opt == 2) fa[i] = read();
add(fa[i],i);
// cin>>s[i];
scanf("%s",&s[i]);
} int m = read();
for(int i = 1;i <= m;++i) {
int id = read();
scanf("%s",ss + 1);
que[id].push_back(make_pair(add(ss),i));
} build(); // for(int i = 0;i < 26;++i) printf("!!%d\n",trie[0][i]); // for(int i = 1;i <= tot;++i) printf("!!%d\n",fail[i]); // printf("!!%d\n",trie[1][0]); AC_dfs(0); // printf("!!!%d %d\n",tot,idtot); for(int i = 1;i <= n;++i) if(!fa[i]) dfs(i,0); for(int i = 1;i <= m;++i) printf("%d\n",ans[i]); return 0;
}

CF1207G Indie Album的更多相关文章

  1. Codeforces 1207 G. Indie Album

    Codeforces 1207 G. Indie Album 解题思路 离线下来用SAM或者AC自动机就是一个单点加子树求和,套个树状数组就好了,因为这个题广义SAM不能存在 \(len[u] = l ...

  2. CodeForces - 1207G :Indie Album(AC自动机 fail树上DFS)

    题意:有N个串,给出的形式是拼接给出,对于第i行:  (1,c)表示字符串i是单个字母c: (2,p,c)表示字符串i=在字符串p后面接上一个字母c. 然后给出M个提问,形式是(i,string).问 ...

  3. CF G. Indie Album 广义后缀自动机+树链剖分+线段树合并

    这里给出一个后缀自动机的做法. 假设每次询问 $t$ 在所有 $s$ 中的出现次数,那么这是非常简单的: 直接对 $s$ 构建后缀自动机,随便维护一下 $endpos$ 大小就可以. 然而,想求 $t ...

  4. CF G. Indie Album AC自动机+fail树+线段树

    这个套路挺有意思的. 把 $trie$ 和 $fail$ 树都建出来,然后一起跑一跑就好了~ #include <queue> #include <cstdio> #inclu ...

  5. ACAM 题乱做

    之前做了不少 ACAM,不过没怎么整理起来,还是有点可惜的. 打 * 的是推荐一做的题目. I. *CF1437G Death DBMS 见 我的题解. II. *CF1202E You Are Gi ...

  6. Educational Codeforces Round 71 (Rated for Div. 2)

    传送门 A.There Are Two Types Of Burgers 签到. B.Square Filling 签到 C.Gas Pipeline 每个位置只有"高.低"两种状 ...

  7. [Codeforces Educational Round 71]Div. 2

    总结 手速场...像我这种没手速的就直接炸了... 辣鸡 E 题交互,少打了个 ? 调了半个小时... 到最后没时间 G 题题都没看就结束了...结果早上起来被告知是阿狸的打字机...看了看题一毛一样 ...

  8. Educational Codeforces Round 71

    https://www.cnblogs.com/31415926535x/p/11460682.html 上午没课,做一套题,,练一下手感和思维,,教育场的71 ,,前两到没啥,,后面就做的磕磕巴巴的 ...

  9. Educational Codeforces Round 71 (Rated for Div. 2) Solution

    A. There Are Two Types Of Burgers 题意: 给一些面包,鸡肉,牛肉,你可以做成鸡肉汉堡或者牛肉汉堡并卖掉 一个鸡肉汉堡需要两个面包和一个鸡肉,牛肉汉堡需要两个面包和一个 ...

随机推荐

  1. linux umask计算方法

    1. umask用于设定默认的新建文件或目录的权限 查看umask当前值命令: umask -p 计算创建出的file权限方法: 如果umask值的每位数都是偶数,使用666按位减umask的值即可 ...

  2. 【2019.8.20 NOIP模拟赛 T2】小B的树(tree)(树形DP)

    树形\(DP\) 考虑设\(f_{i,j,k}\)表示在\(i\)的子树内,从\(i\)向下的最长链长度为\(j\),\(i\)子树内直径长度为\(k\)的概率. 然后我们就能发现这个东西直接转移是几 ...

  3. 趣谈Linux操作系统学习笔记:第二十五讲

    一.mmap原理 在虚拟内存空间那一节,我们知道,每一个进程都有一个列表vm_area_struct,指向虚拟地址空间的不同内存块,这个变量名字叫mmap struct mm_struct { str ...

  4. 从Python安装到语法基础,这才是初学者都能懂的爬虫教程

    Python和PyCharm的安装:学会Python和PyCharm的安装方法 变量和字符串:学会使用变量和字符串的基本用法 函数与控制语句:学会Python循环.判断语句.循环语句和函数的使用 Py ...

  5. python-15-常用文件操作与注册登录练习

    前言 1.常用的文件操作无非就是读或写,但python中没有提供文件修改的功能,是无法实现,但我们可以新增-删除源文件-更改新增文件为源文件名称. 2.使用文件的存储与读取方式来简单完成注册.登录功能 ...

  6. IT兄弟连 Java语法教程 数据类型1

    Java是强类型化的语言 Java是一种强类型话的语言,在开始时指出这一点是很重要的.实际上,Java的安全性和健壮性正是部分来自这一事实.强类型意味着什么呢?首先,每个变量都具有一种类型,每个表达式 ...

  7. MySQL字符类型学习笔记

    目录 一.字符集和字符编码 1.1.字符集 1.2.字符编码 二.字符集排序规则 2.1.排序规则定义 2.2 .排序规则特征 三.CHAR和VARCHAR 3.1.CHAR类型 3.2.VARCHA ...

  8. Python的互斥锁与信号量

    并发与锁 a. 多个线程共享数据的时候,如果数据不进行保护,那么可能出现数据不一致现象,使用锁,信号量.条件锁 b. c.互斥锁1. 互斥锁,是使用一把锁把代码保护起来,以牺牲性能换取代码的安全性,那 ...

  9. SpringBoot中数据加密存储和获取后解密展示AttributeConverter的实现

    1. 需求: 数据库存入数据的时候要加密处理,不同的字段加密方式不同. 界面上展示的时候要解密处理,解密方式相同. 2. 实现方案一: 定义公共的加密解密方法,然后在对应的字段上重写他的getset方 ...

  10. sql server pivot

    SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[YearSalary]( [year] [int] NULL, ...