首页 > 其他分享 >回文自动机(回文树)学习笔记

回文自动机(回文树)学习笔记

时间:2022-08-30 20:23:23浏览次数:96  
标签:int 笔记 st len fail 自动机 pam 回文

回文自动机(回文树)学习笔记

前置知识

建议提前学习 Manacher 算法 和其他任何一种自动机,方便理解,不过不学问题应该也不大。

定义

回文自动机(PAM),也称回文树,是存储一个字符串所有回文子串的数据结构。

PAM 由转移边和后缀链接构成,它的每一个状态都代表着一个回文子串。

设 \(u\stackrel{c}{\to}v\) 表示有一条从状态 \(u\) 到状态 \(v\) 的转移边 \(c\),它的含义是:在状态 \(u\) 代表的回文子串两边都加一个字母 \(c\),可以得到状态 \(v\) 代表的回文子串。

设 \(\operatorname{fail}(u)\) 表示状态 \(u\) 的后缀链接,它的含义是:状态 \(u\) 代表的回文子串的最长回文真后缀对应的状态。

为了方便,我们对于每个状态 \(u\),记录它代表的回文子串的长度 \(\operatorname{len}(u)\)。

在 Manacher 算法中我们知道,回文串长度有奇数有偶数,这是不好处理的。当时我们采用的方法是在相邻两个字符之间插分隔符,把所有回文串都变成奇回文。这里我们换一种方法,建两个状态当根:一个 \(\operatorname{len}\) 为 \(-1\),子树为所有奇回文,称为奇根;一个 \(\operatorname{len}\) 为 \(0\),子树为所有偶回文,称为偶根。

特别地,\(\operatorname{fail}(0)=-1\),而 \(\operatorname{fail}(-1)\) 不重要,因为永远不会失配(左右各加一个字母得到的字符串只有一个字母,显然是回文串)。

例如,\(\texttt{abbab}\) 的 PAM 如下:(粉色边是后缀链接)

构建

考虑类似 SAM 的方式,每次在结尾插入一个字符,构建 PAM。

假设已经构建好前 \(i-1\) 个字符的 PAM,记最长回文后缀对应的状态为 \(lst\),现在要插入第 \(i\) 个字符。

我们从 \(lst\) 开始,不断跳后缀链接,直到一个状态 \(u\) 满足 \(s_i=s_{i-\operatorname{len}(u)-1}\)(记为 \(u=\operatorname{jump}(lst,i)\)),此时我们在状态 \(u\) 代表的回文子串前后添加字符 \(s_i\) 依然是回文子串,而且是新字符串的最长回文后缀。

如果状态 \(u\) 不存在 \(s_i\) 转移边,我们需要添加新状态 \(v\)。新状态 \(v\) 的后缀链接应当为 \(\operatorname{fail}(v)=w:\operatorname{jump}(\operatorname{fail}(u), i)\stackrel{s_i}{\to}w\),可以证明这个节点 \(w\) 一定存在。然后添加转移边 \(u\stackrel{s_i}{\to}v\),并且记录 \(\operatorname{len}(v)=\operatorname{len}(u)+2\) 即可。注意这里加 \(2\),因为在前后各添加了一个字符。

时间复杂度证明

可以通过数学归纳法证明字符串 \(s\) 的本质不同回文子串个数不超过 \(|s|\),因此 PAM 的状态数为 \(\mathcal O(|s|)\),同时显然转移数也为 \(\mathcal O(|s|)\)。

构建的剩余部分只有跳后缀链接的总复杂度不显然为 \(\mathcal O(|s|)\) 的。考虑跳后缀链接的过程,每跳一次使得状态在 \(\operatorname{fail}\) 树上的深度减少一,而添加新状态时最多使状态在 \(\operatorname{fail}\) 树上的深度增加一,因此最多跳 \(2|s|\) 次后缀链接。

综上,PAM 的时间复杂度为 \(\mathcal O(n)\)。

应用

本质不同回文子串个数

显然,答案即为 PAM 的状态数减 \(2\)(去掉奇根和偶根)。

在线求每个位置结尾的回文子串个数 | P5496 【模板】回文自动机(PAM)

一个位置结尾的回文子串个数就是该位置结尾的最长回文子串对应的状态在 \(\operatorname{fail}\) 树上的深度,构建 PAM 时维护一下即可。

代码
// Problem: P5496 【模板】回文自动机(PAM)
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P5496
// Memory Limit: 256 MB
// Time Limit: 500 ms
// 
// Powered by CP Editor (https://cpeditor.org)

//By: OIer rui_er
#include <bits/stdc++.h>
#define rep(x,y,z) for(int x=(y);x<=(z);x++)
#define per(x,y,z) for(int x=(y);x>=(z);x--)
#define debug(format...) fprintf(stderr, format)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
using namespace std;
typedef long long ll;
const int N = 5e5+5;

int n, lst;
char t[N];
template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}
struct State {
	int len, fail, nxt[26];
	int dis; //
};
struct PAM {
	State st[N+5];
	char s[N];
	int sz, lst;
	void init() {
		st[0].len = 0;
		st[0].fail = 1;
		st[1].len = -1;
		st[1].fail = 0;
		sz = 1;
	}
	int jump(int u, int i) {
		while(i - st[u].len - 1 < 1 || s[i-st[u].len-1] != s[i]) u = st[u].fail;
		return u;
	}
	void extend(int i) {
		int u = jump(lst, i), c = s[i] - 'a';
		if(!st[u].nxt[c]) {
			st[++sz].fail = st[jump(st[u].fail, i)].nxt[c];
			st[u].nxt[c] = sz;
			st[sz].len = st[u].len + 2;
			st[sz].dis = st[st[sz].fail].dis + 1; //
		}
		u = st[u].nxt[c];
		lst = u;
	}
}pam;

int main() {
	scanf("%s", t+1);
	n = strlen(t+1);
	pam.init();
	rep(i, 1, n) {
		if(i == 1) pam.s[i] = t[i];
		else pam.s[i] = (t[i] + lst - 97) % 26 + 97;
		pam.extend(i);
		printf("%d%c", lst=pam.st[pam.lst].dis, " \n"[i==n]);
	}
	return 0;
}

求每个回文子串的出现次数 | P3649 [APIO2014] 回文串

建出回文树,然后用类似 SAM 统计子串出现次数的方法倒着 DP 一遍。

注意每次访问到一个节点就要把 \(cnt\) 加一,否则会 WA 第七个点。

代码
// Problem: P3649 [APIO2014] 回文串
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3649
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

//By: OIer rui_er
#include <bits/stdc++.h>
#define rep(x,y,z) for(int x=(y);x<=(z);x++)
#define per(x,y,z) for(int x=(y);x>=(z);x--)
#define debug(format...) fprintf(stderr, format)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
using namespace std;
typedef long long ll;
const int N = 3e5+5;

int n; ll ans;
template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}
struct State {
	int len, fail, nxt[26];
	int cnt;
};
struct PAM {
	State st[N+5];
	char s[N];
	int sz, lst;
	void init() {
		st[0].len = 0;
		st[0].fail = 1;
		st[1].len = -1;
		st[1].fail = 0;
		sz = 1;
	}
	int jump(int u, int i) {
		while(i - st[u].len - 1 < 1 || s[i-st[u].len-1] != s[i]) u = st[u].fail;
		return u;
	}
	void extend(int i) {
		int u = jump(lst, i), c = s[i] - 'a';
		if(!st[u].nxt[c]) {
			st[++sz].fail = st[jump(st[u].fail, i)].nxt[c];
			st[u].nxt[c] = sz;
			st[sz].len = st[u].len + 2;
		}
		u = st[u].nxt[c];
		++st[u].cnt;
		lst = u;
	}
}pam;

int main() {
	scanf("%s", pam.s+1);
	n = strlen(pam.s+1);
	pam.init();
	rep(i, 1, n) pam.extend(i);
	per(i, pam.sz, 1) {
		pam.st[pam.st[i].fail].cnt += pam.st[i].cnt;
		chkmax(ans, 1LL*pam.st[i].cnt*pam.st[i].len);
	}
	printf("%lld\n", ans);
	return 0;
}

标签:int,笔记,st,len,fail,自动机,pam,回文
From: https://www.cnblogs.com/ruierqwq/p/PAM.html

相关文章

  • java学习笔记016 泛型、流
    ######1.泛型Genericsince1.5标示元素类型的参数,泛型不能是基本数据类型泛型不同的引用不能相互赋值静态方法不能使用泛型,因为实例化类的时候要指定泛型,但是静态方......
  • 《伯恩斯焦虑自助疗法》读书笔记3
      对于轻微焦虑的人,基于幽默的治疗法也是一种不错的治疗方法,值得尝试!基于幽默的治疗法有害羞暴露练习法,悖论放大法,幽默想象法!  每个人都有自己的使命!每个人都有自......
  • ffmpeg常用命令笔记
    将mp4视频转换为指定宽高的视频big_buck_bunny.mp4为原视频,big_buck_bunny_1.mp4为新视频,用-s指定宽高ffmpeg-y-i./big_buck_bunny.mp4-s2560*1440big_buck_bunn......
  • kafka3.0.0版学习笔记
    定义传统定义kafka是一个分布式的基于发布/定于模式的消息队列,主要应用于大数据实时处理领域。发布/订阅消息的发布者不会将消息直接发送给特定的订阅者,而是将发布的消......
  • spring 学习笔记二ioc基础
    控制反转IoC(InversionofControl),是一种设计思想,DI(依赖注入)是实现IoC的一种方法控制反转就是将控制权从程序手里转到我们手里,通过我们输入的获取对象,不是通过程序获取......
  • 工作流引擎 Activiti 学习笔记(一)
    一、什么是工作流1.概念工作流(Workflow)是指一类能够完全自动执行的经营过程,根据一系列过程规则,将文档、信息或任务在不同的执行者之间进行传递与执行。2.工作流的实......
  • 【防忘笔记】一个例子理解Pytorch中一维卷积nn.Conv1d
    一维卷积层的各项参数如下torch.nn.Conv1d(in_channels,out_channels,kernel_size,stride=1,padding=0,dilation=1,groups=1,bias=True,padding_mode='zeros',de......
  • Python学习笔记:add、sub、mul、div、mod、pow
    一、介绍add()函数用于向调用者添加对象。使用语法为:DataFrame.add(other,axis='columns',level=None,fill_value=None)实际上等价于dataframe+other的直接使......
  • spring 学习笔记一组成
    Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。组成通过core支持上边其他模块组成Spring框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个......
  • python自学笔记10:while循环和for循环
    条件控制和循环控制是两种典型的流程控制方法,前面我们写了if条件控制,这节讲for循环和while循环。循环是另一种控制流程的方式,一个循环体中的代码在程序中只需要编......