回文自动机(PAM)入门路线 + P3649 【模板】[APIO2014] 回文串(PAM)

news2024/11/25 20:31:06

个人比较推荐的回文自动机学习路径:

回文自动机学习博客:
回文树(讲的最严谨,oiwiki上的)
回文自动机(Palindrome Automanton PAM)(讲的最通俗易懂,知乎上的)
回文自动机(PAM)学习笔记(代码最简洁,洛谷上的)

PAM 的定义:
Palindrome Automaton ( 回文自动机,回文树)是一种能够识别所有回文子串的数据结构,结构十分类似于 ACAM。

  • 节点:节点数至多 N 个,每个节点代表了一种回文串。用 S(u) 表示节点 u 代表的回文串 len[u] = |S(u)|。

  • 后继边 ch:每个后继边上有一个字母。用 trans(u, ch) = v 表示 u 节点有后继边 ch 指向 v 节点。则有 S(v) = chS(u)ch,以及 len[v] = len[u] + 2。

  • 失配边 fa:每个节点都有一一个失配边,用 fail[ul = v 表示 u 节点的失配边指向了 v 节点。则有 S(v) 是 S(u) 的最大 Border,即最长回文后缀(不含本身,如果你学过 KMP 的话你会发现这十分类似于 KMP 的失配指针 )。

  • 奇根和偶根:对于奇回文串和偶回文串需要有两个根节点。由于长度为 2 的回文串是偶根的后继节点,长度为 1 的回文串是奇根的后继节点。因此偶根长度应设为 0,奇根应该设为 -1。方便起见,可以令偶根的失配边指向奇根,奇根的失配边指向偶根。

PAM 的增量构造:

  • PAM 在构造时,实际上有两个核心步骤
    求每个前缀 S[i] 的最长回文后缀,即跳一次 fail 链
    求每个前缀 S[i] 最长回文后缀的最长回文后缀,就是确定它的 fail,即又跳一次 fail 链
  • 步骤 ① 方法:枚举上一个前缀 S[i - 1] 的回文后缀,即 fail 链,每跳到一个节点,观察这个节点代表的子串左边相邻的一个字符(是左边相邻,并不属于这个子串)是否等于 S[i],如果等于就代表两头都可以接得上 S[i],即找到了前缀 S[i] 的最大回文后缀。如果不等,则将子串缩短,继续跳 fail 链。
  • 举个例子:比如某个前缀:abaca,模拟上面的过程后发现,它的最大回文后缀(不包括自身)是 aca,PAM 构造的时候就是求这玩意。
  • 步骤 ② 方法:与步骤 ① 一致,也是跳上一个节点的 fail 链。
  • 注意:不一定每次构造的时候都有新的节点产生,如果当前点的最大回文后缀是个已经出现过的字符串,那就没必要新建了,直接使用即可。

总结一下,PAM 的增量构造实际上就是跳两次 fail 链的过程
在这里插入图片描述

例题:

[APIO2014] 回文串

题目描述

给你一个由小写拉丁字母组成的字符串 s s s。我们定义 s s s 的一个子串的存在值为这个子串在 s s s 中出现的次数乘以这个子串的长度。

对于给你的这个字符串 s s s,求所有回文子串中的最大存在值。

输入格式

一行,一个由小写拉丁字母(a~z)组成的非空字符串 s s s

输出格式

输出一个整数,表示所有回文子串中的最大存在值。

样例 #1

样例输入 #1

abacaba

样例输出 #1

7

样例 #2

样例输入 #2

www

样例输出 #2

4

提示

【样例解释1】

∣ s ∣ \lvert s \rvert s 表示字符串 s s s 的长度。

一个字符串 s 1 s 2 … s ∣ s ∣ s_1 s_2 \dots s_{\lvert s \rvert} s1s2ss 的子串是一个非空字符串 s i s i + 1 … s j s_i s_{i+1} \dots s_j sisi+1sj,其中 1 ≤ i ≤ j ≤ ∣ s ∣ 1 \leq i \leq j \leq \lvert s \rvert 1ijs。每个字符串都是自己的子串。

一个字符串被称作回文串当且仅当这个字符串从左往右读和从右往左读都是相同的。

这个样例中,有 7 7 7 个回文子串 a,b,c,aba,aca,bacab,abacaba。他们的存在值分别为 4 , 2 , 1 , 6 , 3 , 5 , 7 4, 2, 1, 6, 3, 5, 7 4,2,1,6,3,5,7

所以回文子串中最大的存在值为 7 7 7

第一个子任务共 8 分,满足 1 ≤ ∣ s ∣ ≤ 100 1 \leq \lvert s \rvert \leq 100 1s100

第二个子任务共 15 分,满足 1 ≤ ∣ s ∣ ≤ 1000 1 \leq \lvert s \rvert \leq 1000 1s1000

第三个子任务共 24 分,满足 1 ≤ ∣ s ∣ ≤ 10000 1 \leq \lvert s \rvert \leq 10000 1s10000

第四个子任务共 26 分,满足 1 ≤ ∣ s ∣ ≤ 100000 1 \leq \lvert s \rvert \leq 100000 1s100000

第五个子任务共 27 分,满足 1 ≤ ∣ s ∣ ≤ 300000 1 \leq \lvert s \rvert \leq 300000 1s300000

思路:

PAM 可以求出所有本质不同子串,且只有 N 个,那么本题实际上就是要统计每种回文串的出现次数。

可以在构建 PAM 的时候额外维护一个 cnt 数组,表示每个 PAM 节点对应多少个位置的最长回文后缀,也就是每个点有多少次成为了 lstp 节点(lstp 是什么,看代码)。网上有些这部分讲的很好的教程,可以去了解一下。

这样每个节点代表的回文串的出现次数等于,以每个节点为根的 fail 树子树的和。

卡常小技巧:节点编号从大到小就是这棵 fail 树的拓扑排序。

时间复杂度: O ( n ) O(n) O(n)

代码:

#include<bits/stdc++.h>

using namespace std;
const int N = 3e5 + 10;
//lstp:每次刚开始插入字符 s[i] 时,表示的是 s[i - 1] 后缀的最大回文子串节点
//tot:最新创建节点编号
//fa:fa[u] 表示 u 节点代表的回文串的最大回文后缀(不含自己)的节点位置
//len:len[u] 为节点 u 代表的回文串的长度
//cnt:每个节点代表回文子串的出现次数
//ch:转移边
//初始已存在奇偶两根,tot 初始化为 1
int lstp, tot = 1, fa[N], ch[N][26], len[N];
long long cnt[N];
char s[N];

int getfa(int p, int idx)
{
	//检查 s[i - len[p] - 1] 是否等于 s[i]
    while(idx - len[p] - 1 < 0 || s[idx - len[p] - 1] != s[idx]){
    	//不等于,则令 p = fa[p] 重复上面的检查,直到满足。
        p = fa[p];
    }
    //如果等于,得到了以 s[i] 结尾的最长回文串,返回所在节点
    return p;
}

void insert(int c, int idx)	//增量构建PAM,和SAM类似
{
	//先通过 getfa 找出以 s[i] 结尾的最长回文串所在节点 p
    int p = getfa(lstp, idx);
    //找到这个 p 以后,检查 ch[p][s[i]] 是否存在
	//如果不存在
    if (!ch[p][c]) { 
		int np = ++tot;	// trie 树中创建并加入一个新节点 np
		//更新 fa
		int v = getfa(fa[p], idx);	//先找到 p 后缀中最长的回文后缀,且满足前一个字符是 s[i] 的后缀对应节点 v
		fa[np] = ch[v][c];	// fa[np] 指向 v 的儿子 ch[v][s[i]],因为它就是当前后缀 s[1, i] 最长的回文后缀
		//更新 len
		len[np] = len[p] + 2;	//首尾加
		//更新后继边的指向
		ch[p][c] = np;
	}
	//如果存在,说明之前已经处理过了。无需处理,只需根据需求更新一些必要统计信息
    lstp = ch[p][c];
    cnt[lstp]++;	//不是cnt = 1,是++ 表示每个节点有多少次成为过 lstp 节点
}

signed main()
{
    fa[0] = 1, fa[1] = 0, len[0] = 0, len[1] = -1;
    scanf("%s", s);
    for(int i = 0; s[i]; ++i){
        insert(s[i] - 'a', i);
    }
    long long mx = 0;
    for(int i = tot; i > 0; --i){
        cnt[fa[i]] += cnt[i];	//拓扑更新
        mx = max(mx, cnt[i] * len[i]);
    }
    printf("%lld\n", mx);
    
    return 0;
}

空白代码:

#include<bits/stdc++.h>

using namespace std;
const int N = 3e5 + 10;
int lstp, tot = 1, fa[N], ch[N][26], len[N];
long long cnt[N];
char s[N];

int getfa(int p, int idx)
{
    while(idx - len[p] - 1 < 0 || s[idx - len[p] - 1] != s[idx]){
        p = fa[p];
    }
    return p;
}

void insert(int c, int idx)	
{
    int p = getfa(lstp, idx);
    if (!ch[p][c]) { 
		int np = ++tot;	
		int v = getfa(fa[p], idx);
		fa[np] = ch[v][c];
		len[np] = len[p] + 2;	
		ch[p][c] = np;
	}
    lstp = ch[p][c];
    cnt[lstp]++;	
}

signed main()
{
    fa[0] = 1, fa[1] = 0, len[0] = 0, len[1] = -1;
    scanf("%s", s);
    for(int i = 0; s[i]; ++i){
        insert(s[i] - 'a', i);
    }
    long long mx = 0;
    for(int i = tot; i > 0; --i){
        cnt[fa[i]] += cnt[i];	
        mx = max(mx, cnt[i] * len[i]);
    }
    printf("%lld\n", mx);
    
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/461021.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker快速部署springboot项目

有很多开发者在项目部署过程中都会遇到一些繁琐的问题&#xff0c;比如打包、上传、部署等。而使用Docker可以非常方便地解决这些问题。在本文中&#xff0c;将详细讲解如何使用IDEA中的docker打包插件&#xff0c;将代码打包并直接发布到服务器上。这样&#xff0c;我们就可以…

CSS中的 clip 属性

参考&#xff1a;https://baijiahao.baidu.com/s?id1757136902803734131&wfrspider&forpc 作用&#xff1a; clip 属性用来设置元素的形状&#xff0c;用于剪裁绝对定位的元素。当一幅图像的尺寸大于包含它的元素时&#xff0c;clip 属性允许规定一个元素的可见尺寸…

blender 制作城市建筑模型

我不是很会用blender 但是他可以直接制作一篇区域的建筑模型 BlenderGIS插件 城市建筑3D模型自动生成 教程_Zhichao_97的博客-CSDN博客 学习了两种 一种是通过geo.json自己加了一堆mesh 或者geometry 自己用three 做的模型 另一种是用blender 做一个整个的模型直接导入进去 …

Java企业电子招标采购系统源码Spring Boot + Mybatis + 前后端分离 构建企业电子招采平台之立项流程图

项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以及…

LVS+keepalives高可用负载均衡

keepalived是一个基于vrrp协议来实现的LVS服务高可用方案&#xff0c;可解决静态路由器出现的单点故障问题 解决单点故障。 通过主备来保证高可用性&#xff0c; vrrp回忆 vlan区分广播域&#xff0c;vlan if 虚拟接口 vrrp只有一种报文:Advertisement报文&#xff08;通告…

SparkStreaming学习之——无状态与有状态转化、遍历kafka的topic消息、WindowOperations

目录 一、状态转化 二、kafka topic A→SparkStreaming→kafka topic B (一)rdd.foreach与rdd.foreachPartition (二)案例实操1 1.需求&#xff1a; 2.代码实现&#xff1a; 3.运行结果 (三)案例实操2 1.需求&#xff1a; 2.代码实现&#xff1a; 3.运行结果 三、W…

代码在洛谷上跑得慢怎么办?

前言 你有没有试过以下几种情况&#xff1a; 代码在别的OJ上能过&#xff0c;在洛谷上就T了你的代码和同学的几乎相同&#xff0c;但他的AC了&#xff0c;你的却TLE了 遇到这些情况&#xff0c;你可能要花上一个多小时才能解决&#xff0c;甚至难以解决&#xff0c;将问题一…

【springboot-04】ElasticSearch8.7搜索

为什么学&#xff1f;因为它查询速度很快&#xff0c;而且是非关系型数据库 &#xff08;NoSql&#xff09; 一些增删改查已经配置好了&#xff0c;无需重复敲码 ElasticSearch 更新快&#xff0c;本篇文章将主要介绍一些常用方法。 对于 spirngboot 整合 Es 的文章很少&#x…

看了这一篇文章,你还不懂MySQL体系结构,你来找我

前言 工作很长时间了&#xff0c;对于数据库的掌握程度却仅仅停留在表面的CRUD阶段&#xff0c;对于深层次的原理和技术知识了解的少之又少&#xff0c;随着岁数不断的增长。很多时候&#xff0c;出去找工作很迷茫&#xff0c;被面试官问的感觉自己很菜。现在利用工作休息时间&…

微信小程序第五节——登录那些事儿(超详细的前后端完整流程)

&#x1f4cc; 微信小程序第一节 ——自定义顶部、底部导航栏以及获取胶囊体位置信息。 &#x1f4cc; 微信小程序第二节 —— 自定义组件 &#x1f4cc; 微信小程序第三节 —— 页面跳转的那些事儿 &#x1f4cc; 微信小程序第四节—— 网络请求那些事儿 &#x1f61c;作 …

人工智能时代背景下,如何发展与应用自动化测试?

人工智能时代为自动化测试提供了机会和挑战。在发展自动化测试方面&#xff0c;是人工智能领域下的一个应用方向&#xff0c;和无人驾驶、机器人等一样&#xff0c;都是AI技术的应用场景。从技术的发展角度看&#xff0c;自动化测试一共经历了四代发展变化。从最早提出自动化测…

关于 变量

关于局部变量和静态变量&#xff08;基于有一定指针基础&#xff09; #include<stdio.h> void aaa() {int n10;} int main() {printf("%d",n);return 0; } 在这个代码里&#xff0c;很明显会报错&#xff0c;未定义该n标识符&#xff0c;因为这个n是局部变量…

在程序里面执行system(“cd /某个目录“),为什么路径切换不成功?

粉丝提问&#xff1a; 彭老师&#xff0c;问下&#xff0c;在程序里面执行system(“cd /某个目录”)&#xff0c;这样会切换不成功&#xff0c;为啥呢 实例代码&#xff1a; 粉丝的疑惑是明明第10行执行了cd /media操作&#xff0c; 为什么12行执行的pwd > test2.txt 结…

Unity InputField滑动条

InputField增加滑动条效果 类似图中效果 添加一个InputField组件 2 .添加一个Scrollbar放在InputField内 调整属性 调整InputFiled组件属性 需要将Scrollbal添加到InputField的scrollbar上 然后根据美术需求将位置进行调整&#xff0c;记得InputFiled下的Text不要被Scr…

【浓缩概率】浓缩概率思想帮我蒙选择题的概率大大提升!

今天在学习的时候遇到一个很有趣的思想叫作浓缩概率&#xff0c;可以帮我们快速解决一下概率悖论问题&#xff01; 什么是概率 计算概率有下面两个最简单的原则&#xff1a; 原则一、计算概率一定要有一个参照系&#xff0c;称作「样本空间」&#xff0c;即随机事件可能出现…

Docker容器--Consul部署

Docker容器--Consul部署 一、简介1、概述2、Consul两种模式 二、Consul特性1、特性2、应用场景 三、部署Consul集群&#xff08;Server端&#xff09;1、建立Consul服务2、设置代理&#xff0c;在后台启动consul服务端3、查看集群信息 四、Consul部署&#xff08;Client端&…

第4章:运算符

1.算术运算符 ① SELECT 10010,100-35.5,100*2,100/2,100%30 FROM DUAL;②在sql中“”没有连接作用&#xff0c;表示加法运算&#xff0c;字符串转换为数值&#xff08;隐式转换&#xff09;。非数值看作0处理 SELECT 1001 1 FROM DUAL;SELECT 100 a FROM DUAL;③加法运算…

Trie Tree(字典树)例题

字典树. 又称单词查找树&#xff0c; Trie树 &#xff0c;是一种 树形结构 &#xff0c;是一种哈希树的变种。经常被搜索引擎系统用于文本词频统计。. 它的优点是&#xff1a;利用字符串的公共前缀来减少查询时间&#xff0c;最大限度地减少无谓的字符串比较&#xff0c;查询效…

Python 使用chatGPT帮忙写一个有序集类 OrderedSet

需求:需要实现一个有序的集合&#xff0c;像python普通集合一样&#xff0c;除了 它是有序的 我这边穿插着使用了gpt3.5和gpt4,发现确实还是gpt4好用&#xff0c;一分钱一分货啊 问&#xff1a;我的要求是这样&#xff0c;data是一个集合&#xff0c;往里面放了2&#xff0c;…

【大厂面试问题】:飞机绕行地球问题

你的阅读是我最大的动力 目录 你的阅读是我最大的动力 问题描述&#xff1a; 引出思路&#xff1a; 一台加油飞机 两台加油飞机 返航方案一&#xff1a;加油机I、II同时起飞。 返航方案二&#xff1a;加油机I先起飞加油机II再起飞 答案 不直接说答案&#xff0c;一步一…