题目

给你一棵每条边从父亲指向儿子的树,每条边上面有一个字母。

从树上的任意一点出发,走出的路径就是对应一个子串。

(这不是\(Trie\),因为每个父亲可能会连出字母相同的边)

再给你一个字符串\(S\),让你求\(S\)的子串和树上路径的对应个数。


思考历程

一开始以为路径是从根节点出发,于是我就想,这难道不是一个AC自动机的裸题吗?

啪啪啪地就把AC自动机打了上去……

然后发现样例过不去……

于是终于理解完题目大意,开始死磕。

但是一直都没有放弃AC自动机的做法。

一波乱搞后,我终于爆0了。


正解

后来才意识到其实这题是后缀自动机。

于是就开始自己刚后缀自动机的做法了……

大体思路是处理出树上的每个子串的出现次数,然后跟\(S\)的子串进行匹配。

先想想前面的这个问题。

首先,由于这是一棵树,所以就对整棵树建广义后缀自动机

至于这是什么东西……我想就不该在这里赘述了。

然后有个点\(i\)从树的根部往下跑。跑的时候将\(root\)到\(i\)路径所组成的字符串的所有后缀统计进来

\(i\)在跑的时候也有个点\(t\)在后缀自动机上跑。对于每对\((i,t)\),就将\(t\)的\(fail\)链上的所有统计次数\(num+1\)。

显然,由于节点所表示的子串的\(right\)集合相同,所以\(right\)集合大小相同,所以它们的出现次数也相同。所以\(num\)表示的是这个节点的所有子串各自出现的次数。

在实现的时候,可以先在\(t\)点打个标记,跑完之后\(fail\)树上标记上传就好了(如果你喜欢就打树链剖分吧~~(手动滑稽))。

这样我们就处理出树上的每个子串的出现次数了,接下来是匹配的问题。

也是枚举个\(i\)表示子串的右边界,也有个\(t\)在后缀自动机上面跳。

设\(l\)为以\(i\)为结尾的在树中出现过的最长的子串长度。

显然,\(t\)在跳的过程中保证了\(t_{minlen}\leq l\leq t_{maxlen}\)。

由于子串\(S_{i-l+1..i}\)的所有后缀都要记录进答案中,而\(t\)的\(fail\)链上所有节点表示的子串都是它的后缀,所以就将\(fail\)链上的出现次数全部加上(即为累加\(num*(maxlen-minlen+1)\))。具体实现的时候可以用个前缀和将祖先的出现次数全部存下来,记作\(sum\)。

我们计算的是长度在\([1,l]\)的后缀,可以拆成\([1,t_{minlen}-1]\)和\([t_{minlen},l]\)这两段,前者的答案是\(t_{fail_{sum}}\),后者的答案为\(t_{num}(l-t_{fail_{maxlen}})\),加起来就行了。

然后问题就变成了求\(l\)的具体值。

这实际上很简单,只不过我之前想了很久,甚至恨不得再打一个AC自动机来求。

在一开始的时候\(l=0\)。后来,当\(t\)可以往前走的时候,\(l\)的值也相应地\(+1\)。当\(t\)要跳\(fail\)的时候,就将\(l\)和\(t_{fail_{maxlen}}\)取个最小值。具体原因不再赘述。

于是这道题就没了……


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 800010
#define LEN 8000010
int n;
struct EDGE{
int to,c;
EDGE *las;
} e[N];
int ne;
EDGE *last[N];
struct Node{
Node *c[3],*fail;
int len;
int num;
long long sum;
} d[N*2];
int cnt;
Node *S,*T;
Node *mp[N];
inline void insert(int ch){
if (T->c[ch] && T->c[ch]->len==T->len+1){
T=T->c[ch];
return;
}
Node *nw=&d[++cnt],*p,*q;
nw->len=T->len+1;
for (p=T;p && !p->c[ch];p=p->fail)
p->c[ch]=nw;
if (!p)
nw->fail=S;
else{
q=p->c[ch];
if (p->len+1==q->len)
nw->fail=q;
else{
Node *clone=&d[++cnt];
memcpy(clone,q,sizeof(Node));
clone->len=p->len+1;
for (;p && p->c[ch]==q;p=p->fail)
p->c[ch]=clone;
nw->fail=q->fail=clone;
}
}
T=nw;
}
inline void build(){
static int q[N];
T=S=&d[++cnt];
int head=0,tail=1;
q[1]=1;
mp[1]=S;
do{
int x=q[++head];
for (EDGE *ei=last[x];ei;ei=ei->las){
T=mp[x];
insert(ei->c);
mp[ei->to]=T;
q[++tail]=ei->to;
}
}
while (head!=tail);
}
inline void hang(){
static pair<int,Node*> q[N];
int head=0,tail=1;
q[1]={1,S};
do{
++head;
int x=q[head].first;
Node *ht=q[head].second;
for (EDGE *ei=last[x];ei;ei=ei->las){
Node *tt=ht;
while (tt!=S && !tt->c[ei->c])
tt=tt->fail;
if (tt->c[ei->c])
tt=tt->c[ei->c];
tt->num++;
q[++tail]={ei->to,tt};
}
}
while (head!=tail);
}
Node *q[N];
bool vis[N];
inline void get_sum(){
int head=0,tail=1;
q[1]=S;
vis[1]=1;
do{
Node *x=q[++head];
for (int i=0;i<3;++i)
if (x->c[i] && !vis[x->c[i]-d]){
vis[x->c[i]-d]=1;
q[++tail]=x->c[i];
}
}
while (head!=tail);
for (int i=tail;i>=2;--i){
Node *x=q[i];
x->fail->num+=x->num;
}
for (int i=2;i<=tail;++i){
Node *x=q[i];
x->sum=x->fail->sum+1ll*x->num*(x->len-x->fail->len);
}
}
char s[LEN];
int main(){
scanf("%d",&n);
for (int i=2;i<=n;++i){
int fa;
char c[2];
scanf("%d%s",&fa,c);
e[ne]={i,*c-'a',last[fa]};
last[fa]=e+ne++;
}
build(),hang(),get_sum();
Node *t=S;
scanf("%s",s);
long long ans=0;
int len=0;
for (char *ch=s;*ch;++ch){
while (t!=S && !t->c[*ch-'a']){
t=t->fail;
len=min(len,t->len);
}
if (t->c[*ch-'a']){
t=t->c[*ch-'a'];
len++;
}
if (t!=S)
ans+=t->fail->sum+1ll*t->num*(len-t->fail->len);
}
printf("%lld\n",ans);
return 0;
}

总结

这也算是一道后缀自动机的模板题吧……

看来还是不够熟练啊……

[JZOJ3402] 【GDOI2014模拟】Pty的字符串的更多相关文章

  1. GDOI2014模拟pty爬山(mountain)

    pty爬山(mountain) 在Pty学校附近,有一座名之为岳之麓的高山.Pty很喜欢和(哔--)一起爬山.山的平面模型如下:山由一个顶点集:A1,A2-An给定,保证Ai的x单调递增.我们将Ai和 ...

  2. 【GDOI2014模拟】JZOJ2020年8月14日提高组 服务器

    [GDOI2014模拟]JZOJ2020年8月14日提高组 服务器 题目 Time and Memory Limits Description 我们需要将一个文件复制到n个服务器上,这些服务器的编号为 ...

  3. 【GDOI2014模拟】JZOJ2020年8月14日T2 网格

    [GDOI2014模拟]JZOJ2020年8月14日T2 网格 题目 Time and Memory Limits Description 某城市的街道呈网格状,左下角坐标为A(0, 0),右上角坐标 ...

  4. bzoj3756: Pty的字符串

    #include <iostream> #include <cstdio> #include <cstring> #include <cmath> #i ...

  5. 计蒜客蓝桥杯模拟赛 后缀字符串:STL_map+贪心

    问题描述 一天蒜头君得到 n 个字符串 si​,每个字符串的长度都不超过 10. 蒜头君在想,在这 n 个字符串中,以 si​ 为后缀的字符串有多少个呢? 输入格式 第一行输入一个整数 n. 接下来  ...

  6. 洛谷 P3695 CYaRon!语 题解 【模拟】【字符串】

    大模拟好啊! 万一远古计算机让我写个解释器还真是得爆零了呢. 题目背景 「千歌です」(我是千歌).「曜です」(我是曜).「ルビィです」(我是露比).「3人合わせて.We are CYaRon! よろし ...

  7. Pty的字符串(string)

    题目描述 在神秘的东方有一棵奇葩的树,它有一个固定的根节点(编号为1).树的每条边上都是一个字符,字符为a,b,c中的一个,你可以从树上的任意一个点出发,然后沿着远离根的边往下行走,在任意一个节点停止 ...

  8. Hidden (NOIP模拟赛)(字符串模拟QAQ)

    原题传送门 神奇的题目诶 原来以为字符串比较一定要O(NlogN) 结果发现可以均摊O(N) 首先我们来讲一讲原理 我们有3个指针i,j,k i=0,j=1,k=0 一开始我们不断对k+1直到找到ch ...

  9. 常州模拟赛d4t3 字符串划分

    题目描述 给你一串由小写字母组成的字符串,希望你把它划分成一些小段,使得每一小段字符串中的字母 都不相同,并且希望分的段数尽量少. 然后,把这些小段按字典序排序后输出,中间由一个空格分隔. 例如:字符 ...

随机推荐

  1. 5-MySQL-Ubuntu-操作数据库的基本操作语句

    注意: (1)每一条sql语句都是以分号(;)结尾! (2)数据库的默认charset不支持中文,所以每次在创建数据库的时候要指定字符集charset=utf8; (一) 查看当前时间: select ...

  2. VMware1设备与主机共享网络的问题

    问题的提出: 最近需要用到VMware1设备来配置网络,顺便将VMware1设备与主机进行共享网络,这样master就能直接访问网络了,但是原本以为直接在wlan设备上选择网络共享就行了,但是却没法收 ...

  3. wkhtmltopdf linux下html转pdf

    https://blog.csdn.net/wujunlei1595848/article/details/91129197 https://github.com/wkhtmltopdf/wkhtml ...

  4. 用python 编写redis 暴力破解密码的程序

    本文摘自http://blog.knownsec.com/2015/11/analysis-of-redis-unauthorized-of-expolit/ import redisimport l ...

  5. leetcood学习笔记-107-二叉树的层次遍历二

    题目描述: 方法一: class Solution(object): def levelOrderBottom(self, root): """ :type root: ...

  6. Spring Data之Example<>

    简单CRUD之Example动态查询 简单介绍 (部分口水话,部分来自网络,代码永远自产) 使用过Spring全家桶的各位大佬应该都知道,Spring Data这个是Spring对持久层框架的封装,比 ...

  7. 50 ubuntu下pcl编译以及用 VSCode配置pcl / opencv开发环境

    0 引言 最近在VSCode下搞开发,于是将pcl库迁移到这个环境下,用来跑一些依赖pcl的开源的代码以及自己做一些快速开发等. 1 pcl编译 主要参考了这篇博客,链接如下. https://blo ...

  8. sqoop一些常用命令及参数

    常用命令列举 这里给大家列出来了一部分Sqoop操作时的常用参数,以供参考,需要深入学习的可以参看对应类的源代码. 序号 命令 类 说明 1 import ImportTool 将数据导入到集群 2 ...

  9. NX二次开发-char*转换成CString,多字节转换成Unicode使用方法

    //定义一个结构体记录 struct group { CString text; //定义一个CString std::vector<tag_t> boudaries; std::vect ...

  10. Linux下同一目录内文件和目录为什么不能同名?

    问题描述: 如果事先有叫‘A’的文件夹存在,则不允许建立叫‘A’的文件: 同理若先有叫‘b’的文件存在,一样不允许建立叫‘b’的文件夹. 原因很简单,因为Linux下一切都是文件,一个目录归根到底还是 ...