首页 > 编程语言 >进行一个字符串算法的总结

进行一个字符串算法的总结

时间:2024-06-17 22:22:38浏览次数:26  
标签:总结 子串 字符 int texttt 算法 字符串 回文

本文参考 字符串基础 by Alex_Wei。


Manacher 算法

这玩意是用来求回文子串的。

虽然一个字符串的子串数量是 \(O(n^2)\) 级别的,但是回文串有更好的描述方式。

注意到若一个子串 \([l, r]\) 是以 \(mid\) 为回文中心的回文串,那么将左端点和右端点朝着 \(mid\) 方向挪动若干单位也是回文串。因此我们只需要记录回文中心和最大的回文半径就可以得到所有回文子串的信息。

回文中心的个数是 \(O(n)\) 级别的,所以能高度压缩地记录回文串的信息。

Manacher 算法就是一个 \(O(n)\) 的时间复杂度求出每个点的回文半径的算法。


随便敲个字符串 \(\texttt {aazuusuuzazza}\) 出来。

观察到偶数长度的回文子串的回文中心在两个字符之间,而奇数长度的回文子串回文中心是一个字符。

为了把这两种子串统一起来,我们在两个字符之间加入一个不会在串中出现的字符,例如 \(\texttt{@}\)。

然后我们得到了串串 \(\texttt {@a@a@z@u@u@s@u@u@z@a@z@z@a@}\)。

设 \(R_i\) 为新串的第 \(i\) 个字符的最长回文半径,那么我们要求的是 \(R\) 数组。

手玩一下,以第 \(i\) 个字符为回文中心的最长串串的长度就是 \(R_i - 1\)。

考虑一种暴力。枚举回文中心后尝试向两边扩展,能扩展则扩展。

因为回文串级别是 \(O(n^2)\) 的,所以这个算法的时间复杂度是 \(O(n^2)\) 的。

但是我们发现回文串有些比较好的性质。

举个例子,我们在上面抓个子串出来。就决定是你了,\(\texttt {@a@a@z@u@u@s@u@u@z@a@z@}\)。

假设我们已经知道了最中间的那个 \(\texttt{s}\) 的 \(R_i\) 是 \(10\),能扩展到最远的地方是倒数第二个 \(\texttt @\)。

再考虑这个 \(\texttt s\) 后面三个字符的那个 \(\texttt @\)。我们的 \(R_i\) 还用从 \(0\) 开始枚举吗?

把这个 \(\texttt @\) 对称过去,找到 \(\texttt s\) 前面三个的 \(\texttt @\)。我们求出了这个 \(\texttt @\) 的最长回文半径是 \(3\)。

那对称地,这个 \(\texttt @\) 的最长回文半径至少是 \(3\),这样我们将 \(R_i\) 赋初始值为 \(3\) 就行了。

再考虑倒数第四个字符的那个 \(\texttt a\)。对称过去是第二个字符的 \(\texttt a\),其 \(R\) 为 \(2\),所以赋这个 \(a\) 初值为 \(2\)。这个时候就能继续扩展到 \(4\)。然后能扩展到最远的地方更远了,所以更新当前对称中心和扩展到的最远地方。

实现上,设当前扩展到最远的地方为 \(r\),对称中心为 \(c\)。

如果当前字符 \(i > r\),那么令 \(R_i = 0\)。否则令 \(R_i = \min\{R_{2c - i}, r - i + 1\}\)。

然后暴力扩展,更新 \(r\) 和 \(c\)。

对于每个 \(r\),在 \(i < r\) 的时候更新是 \(O(1)\) 的,而 \(r\) 最多变化 \(n\) 次。

所以时间复杂度是 \(O(n)\) 的。


P3805 【模板】manacher

把 \(R\) 数组求出来后对 \(R_i - 1\) 求 \(max\) 即可。

namespace azus{
	int n;
	string s, a;
	int R[23000005];
	int main(){
		cin >> s;
		n = s.length();
		for(int i = 0; i < n; i ++)
			a += "@", a += s[i];
		a = a + "@"; n = a.length(); a = " " + a;
		R[1] = 1;
		int r = 1, c = 1, ans = 0;
		for(int i = 1; i <= n; i ++){
			if(i <= r)
				R[i] = min(r - i + 1, R[2 * c - i]);
			while(i - R[i] >= 0 && i + R[i] <= n && a[i - R[i]] == a[i + R[i]])	R[i] ++;
			if(i + R[i] - 1 > r) r = i + R[i] - 1, c = i;
			ans = max(ans, R[i] - 1);
		}
		cout << ans;
		return 0;
	}
}

意外的发现 a += "@"a = a + "@" 有很大区别,前者复杂度 \(O(1)\),后者还要加上复制串串的复杂度所以是 \(O(n)\)。

P3501 [POI2010] ANT-Antisymmetry

和回文串性质一样,在 while 循环中改改条件,变成扩展 ANT-Antisymmetry 串就行了。

P4555 [国家集训队] 最长双回文串

对于每个 \(\texttt @\),统计以它为右端点的最长回文串和以它为左端点的最长回文串。

但是我们统计的 \(R_i\) 是一个点能扩展到的最长长度。

定义一个回文串是饱和的,如果这个字符串不能再扩展了。我们发现有些点结尾或开头的不饱和字符串比包和字符串还长。

所以简单递推更新一下即可。

//Manacher 中
ls[i + R[i] - 1] = max(ls[i + R[i] - 1], R[i] - 1);
rs[i - R[i] + 1] = max(rs[i - R[i] + 1], R[i] - 1);
//进行一个简单递推和统计方案
for(int i = n; i >= 3; i -= 2)
	ls[i] = max(ls[i + 2] - 2, ls[i]);
for(int i = 3; i <= n; i += 2)
	rs[i] = max(rs[i - 2] - 2, rs[i]);
for(int i = 1; i <= n; i ++)
	if(a[i] == '@' && ls[i] && rs[i]) ans = max(ans, ls[i] + rs[i]);

P1659 [国家集训队] 拉拉队排练

把每个长度的奇回文串的长度用桶统计一下。

然后用快速幂直接做就行了。

for(int i = 2; i <= n; i += 2)
	t[R[i] - 1] ++;
for(int i = n - (!(n & 1)); i >= 1; i --){
	if(!t[i]) continue;
	if(k <= t[i]) {ans = ans * ksm(i, k) % P, k = 0; break;}
	if(i == 1) break;
	k -= t[i], ans = ans * ksm(i, t[i]) % P;
	t[i - 2] += t[i], t[i] = 0;
}

P5446 [THUPC2018] 绿绿和串串

这东西乍看下去有点复杂。

观察下,首先发现如果有一个以最后一个字符结尾的回文子串,那么以这个子串回文中心翻转一下一定满足条件。

然后,如果以 \(i\) 为轴翻转的串满足条件,那么如果有个字符串能翻转造出字符串 \([1, i]\) 那也能满足条件。

这个字符串必须从 \(1\) 开始翻转,所以这个字符串的回文中心固定为 \(\frac 12 (1 + i)\) 这个位置。

从后往前递推判断每个字符是否能满足条件即可。

//Manacher 中
if(a[i] != '@' && i + R[i] - 1 == n) flg[i] = 1;
//进行一个递推
for(int i = n - 1; i >= 2; i -= 2){
	if(flg[i]){
		int u = 1 + (i + 1) / 2;
		if(a[u] == '@') continue;
		if(u + R[u] - 1 == i + 1) flg[u] = 1;
	}
}
for(int i = 1; i <= n; i ++)
	if(flg[i]) cout << i / 2 << " ";

标签:总结,子串,字符,int,texttt,算法,字符串,回文
From: https://www.cnblogs.com/AzusidNya/p/18253335

相关文章

  • 第8周总结
    第8周总结:本周,我继续开发地铁查询系统的安卓端。根据Web端的功能和用户反馈,我设计并实现了安卓端的查询和显示功能。通过使用安卓的RecyclerView和Adapter,我实现了数据的动态加载和显示,并结合SQLite数据库存储和管理用户数据。在界面设计上,我尽量保持与Web端一致,以提供统一的用户......
  • 第2周总结:
    第2周总结:本周,我继续深入学习安卓开发知识,并开始着手编写个人作业。重点学习了安卓四大组件(Activity、Service、BroadcastReceiver、ContentProvider)的基本使用方法,尤其是Activity的生命周期和Intent的传递机制。同时,我开始设计并编写一个简单的安卓应用,实现基本的增删改查(CRUD)功......
  • 持续总结中!2024年面试必问 20 道设计模式面试题(二)
    上一篇地址:持续总结中!2024年面试必问20道设计模式面试题(一)-CSDN博客三、请描述单例模式(SingletonPattern)及其使用场景。单例模式是一种创建型设计模式,用于确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。这种模式在软件系统中非常常见,因为它提供了一种控制实......
  • 第1周总结
    第1周总结本周,我开始了安卓开发知识的学习,重点集中在基础概念和环境配置上。我通过阅读官方文档和观看视频教程,掌握了安卓开发的基本原理和流程。安装和配置了安卓开发环境,包括AndroidStudio的下载和安装、JDK的配置以及模拟器的设置。通过编写简单的HelloWorld应用,熟悉了安卓......
  • 目标检测算法之YOLO(YOLOv4-YOLOv6)
    YOLO算法理解YOLOv4BagofspecialsCross-stagepartialconnections(CSP)SpatialPyramidPooling(SPP)PANpath-aggregationblockSAMMishactivationMulti-inputweightedresidualconnections(MiWRC)BagoffreebiesMosaic方法ClasslabelsmoothCmBN和Dynamicmini-ba......
  • MD5哈希加密算法
    [TOP]简介MD5(Message-DigestAlgorithm5)是一种被广泛使用的密码散列函数,它可以产生出一个128位(16字节)的散列值(hashvalue),用于确保信息传输完整一致。MD5并不是一种加密算法(因为它不可逆),而是一种摘要算法或哈希算法。以下是MD5加密(更准确地说是哈希)原理的简要概述:说明输入:MD......
  • 代码随想录算法训练营第38天|● 理论基础 ● 509. 斐波那契数● 70. 爬楼梯 ● 746.
    动态规划理论基础动态规划,英文:DynamicProgramming,简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。所以动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的,动态规划做题步骤确定dp数组(dptable)以及......
  • python字符串的一些操作实例
    已知字符串a=“aAsomr3idd4HGHbigs7Dlsf9YeAF”,要求如下1.请将a字符串的大写改为小写,小写改为大写。2.将a字符串的数字取出,并输出成一个新的字符串。3.将a字符串中的内容反向输出4.打印a字符串中所有奇数位上的字符(下标是1,3,5,7…位上的字符)5.将a字符串的所有偶数位上......
  • 复习加总结
    Markdown学习标题三级标题四级标题字体粗体:俩*hello,World斜体:一个**hello,World*斜体加粗三个*hello,World删除线:两个~hello,World引用始作俑者没有受罚,仅仅是受害者再受害一次罢了,最多也就是管理/梦境支配者的人类在人类世界的走狗棋子被带走,毫无意义。大于号>分割......
  • 【论文复现|智能算法改进】基于多策略的改进蜜獾算法及其应用
    目录1.算法原理2.改进点3.结果展示4.参考文献5.代码获取1.算法原理【智能算法】蜜獾算法(HBA)原理及实现2.改进点限制反向学习机制在挖掘模式和蜂蜜模式不同路径更新的基础上引入限制反向学习机制,在算法迭代时,对当前的种群个体进行限制反向学习,生成新的限制反向解......