「manacher」学习笔记
定义与基本求法
-
定义
又名 马拉车 ,用于处理子串回文问题。
-
基本求法
暴力判断每个子串是否是回文是
的,根据其对称性优化为 依旧是不优秀的。马拉车便是解决这种单一问题的算法,具有局限性,但同时是解决这种问题的不二选择。
枚举回文串的中点,例如
的中点为 ,依次为基础进行下一步判断。那么这里发现如果回文串长度为偶数则无法判断,于是将其进行下述优化:
例如
,将其每一个空隙插入一个 (两边也插): ,这样其中点就变成了 ,长度也变为奇数。同时为了方便,对于奇数长度的回文也不放过,如 ,每一个长度为 的回文长度都变为 ,这样无一例外的全部变为奇数长度。先设
为以 为中点的回文的最长半径,例如: ,以中间的 为中点,则其最长半径为 ,即 的长度。再以
为中心,不断向两边扩张。定义 为之前找到的回文串最靠右的右边界, 表示这个最大右边界对称轴的位置。那么
在 以内时,显然时存在对称性的, ,前提是其右端点必须在 以内,否则其右端点只能到 , 也只能 。继续向外扩展不然他就只能自力更生了,
,再进一步向外扩展。只要
就说明可以继续向外扩展了, 。那么在扩展的过程中,超出
了,就刷新 的值,同时 也成了新的 。其实把马拉车理解了会发现这个东西很简单,很好理解,没有那么抽象。
于是就通过上述方法求出了每一个
。参考下面一些图:
综上所述:
int init() { int len=strlen(str); s[0]='@',s[1]='#'; int j=2; for(int i=0;i<len;i++) s[j++]=str[i],s[j++]='#'; s[j]='\0'; return j; } void manacher() { int ans=-1,mx=0,id=0,len=init(); for(int i=1;i<len;i++) { if(i<mx) p[i]=min(p[id*2-i],mx-i); else p[i]=1; while(s[i+p[i]]==s[i-p[i]]) p[i]++; if(p[i]+i>mx) mx=p[i]+i,id=i; } }
例题
模板
- 题目链接
- 上面的代码找最大的
即可。(因为 )
神奇项链
-
题面:
多个回文串拼在一起,且相同部分可重叠,如
可以拼成 或 ,给定一字符串 ,求拼成该字符串最少需要多少步。 -
解法:
我们是将每个串插上
的,所以就算不另其重叠, 也是会重叠的,这就解决了判断重叠的问题。那么对于其每次拼定会存在重叠,所以只要求其最少产生多少重叠即可。
那么用到马拉车,先求出每个
,随后就可以求出每一个回文的左右端点,将这些端点以左端点前后顺序排序。这样使每两个回文重叠部分尽可能的小。确定一回文
的右端点(当然也是尽可能靠右的,即当前左端点允许的,右端点最靠后的回文右端点),枚举后面回文的左端点,使其右端点端点尽可能的靠后,直至左端点与 右端点重合,在此过程中,刷新最靠后的右端点。举个例子,方便理解:
有次可见上述文字描述的正确性,以及无论拼合时无论是否重叠,加上
之后都是有重叠的。 -
代码如下:
#include<bits/stdc++.h> #define int long long #define endl '\n' using namespace std; const int N=1e6+10,P=1e9+7; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=1; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } char str[N],s[N]; int p[N],len; struct aa { int sta,en; }a[N]; bool cmp(aa a,aa b) {return a.sta<b.sta;} int init() { int len=strlen(str); s[0]='#'; int j=1; for(int i=0;i<len;i++) s[j++]=str[i],s[j++]='#'; s[j]='\0'; return j; } void manacher() { int ans=-1,mx=0,id=0,len=init(); for(int i=0;i<len;i++) { if(i<mx) p[i]=min(p[id*2-i],mx-i); else p[i]=1; while(s[i+p[i]]==s[i-p[i]]) p[i]++; if(p[i]+i>mx) mx=p[i]+i,id=i; } } int cover() { int len=init(); for(int i=0;i<len;i++) a[i].sta=i-p[i]+1, a[i].en=i+p[i]-1; stable_sort(a,a+len,cmp); int far=0,ans=0,i=0; for(i=0;a[i].sta<=0;i++) if(a[i].en>a[far].en) far=i; while(i<len) { ans++; int x=far; while(a[i].sta<=a[far].en&&i<len) { i++; if(a[i].en>a[x].en) x=i; } far=x; } return ans; } signed main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif while(scanf("%s",str)!=EOF) { memset(p,0,sizeof(p)); memset(a,0,sizeof(a)); manacher(); cout<<cover()-1<<endl; } } -
由此可见,马拉车也可以作为求具体问题的辅助算法存在。
-
题面:
反对称字符串,仅由
组成,如 ,将 取反后,再反过来和原串一样。现给定一个字符串,求它有多少个子串是反对称的。
-
解法:
回文,但是反的回文。
不难发现,如果其值反对称的,其长度必定是偶数。
举个反例:
显然无法是反对称的。那么转换到
中,就是其对称中心必定是 ,那么跑 时,只管对称中心是 的情况就行了。下一步思考怎么跑这个回文,有两个方法:
-
先建一个与其相反的字符串,如
,然后跑马拉车时改成 就行了。 -
当然也可以不用再建一个,他要是匹配成功一定是一个
一个 ,那么判 就行了,这样会把 跳过去,所以让 ,还能快一点。
最后把所有对称中心是
的 加起来就行了。至于把他 是因为他是插入了 的,长度也就 了。如果代码中直接把对称中心不是
给 continue 了,那把所有的 加在一起也可以,因为对称中心不是 的 肯定是 。打就完事了,挺简单的,但是别手残
不要手残 -
-
代码如下:
#include<bits/stdc++.h> #define int long long #define endl '\n' using namespace std; const int N=1e6+10; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=1; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } string str,s=" #"; int n,p[N],ans,len; void init() { for(int i=0;i<n;i++) s+=str[i], s+='#'; } void manacher() { int mx=0,id=0; len=s.size()-1; for(int i=1;i<=len;i++) { if(s[i]!='#') continue; if(i<mx) p[i]=min(p[(id<<1)-i],mx-i); else p[i]=1; while(s[i+p[i]]+s[i-p[i]]=='0'+'1'&&i+p[i]<=len&&i-p[i]>=1) p[i]+=2; if(p[i]+i>mx) mx=p[i]+i,id=i; } } signed main() { #ifndef ONLINE_JUDGE freopen("ant13a.in","r",stdin); freopen("out.txt","w",stdout); #endif read(n); cin>>str; init(); manacher(); for(int i=1;i<=len;i++) ans+=p[i]>>1; cout<<ans; }
总结
作为一个相对简单且应用范围不广的算法,没有找到别的经典例题了,到这儿就结束了。
打完博客对于其理解还是有很大进步的。
再次吐槽一下网课质量,依旧是网上找资料进行学习的。
例题的图是自己画的,上面基本求法的图是网上找的,感觉不错就扣下来了。
神奇项链这题感觉也是挺不错的,教练得知
最后挺不明白这玩意为啥是
还有,别手残!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效