Manacher例题问题汇总

本篇随笔面向个人

本来以为回文串很简单,但是没有做对应的练习前下此定论为时过早。

https://www.ybtoj.com.cn/contest/75

模板

虽然例题中也没有模板题(因为太简短了……),但是有必要预先打一遍。

步骤

  1. 将原字符串头部插入$,尾部插入@\0,再将间隔中插入#(包括$后和@前也要有#);比如说如果原串为abc,则变换为$#a#b#c#@

  2. 定义变量mx,p,r[i]。

  3. 遍历新字符串。首先确定当前r[i]的下界为\(i<mx?\min(r[2p-i],mx-i):1\),接着尝试暴力向两边拓展,最后看看能不能更新\(mx\),若能则同时更新\(p\)。

代码

void manacher()
{
int mx=1,p=1;
for(int i=1;i<str.size()-1;i++)
{
r[i]=i<mx?min(mx-i,r[2*p-i]):1;
while(str[i-r[i]]==str[i+r[i]]) r[i]++;
if(mx<i+r[i])
{
p=i;
mx=i+r[i];
}
}
}

注意事项

不建议用string(虽然上面的代码的确是string)

注意\(\min\)中的\(r[2*p-i]\),不是\(2*p-1\),同时记得下界是由对称位置的r决定而不是下标决定。

为什么是\(2*p-i\)?设\(i,j\)关于\(p\)对称,则\(\frac{i+j}{2}=p\),所以\(j=2*p-i\)。

优化/简化

预处理可以用一句话解决

s[0]='$';scanf("%d %s",&n,s+1);for(int i=n;i>=1;i--) s[2*i]=s[i],s[2*i+1]='#';s[1]='#';

这同时也说明了char[]相对于string的优点之一。

例题

【例题1】不交回文串

给一个字符串\(S\),问其有多少对不相交的回文子串。

先跑模板,计算每个位置上最长回文串。再差分转前缀和地求以i开头/结尾的回文串个数,再对其中一个做前缀和,用另一个乘一下即为答案。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<queue>
#include<vector>
#define IL inline
#define re register
#define LL long long
#define ULL unsigned long long
#ifdef TH
#define debug printf("Now is %d\n",__LINE__);
#else
#define debug
#endif
using namespace std; template<class T>inline void read(T&x)
{
char ch=getchar();
int fu;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') fu=-1,ch=getchar();
x=ch-'0';ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
x*=fu;
}
inline int read()
{
int x=0,fu=1;
char ch=getchar();
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') fu=-1,ch=getchar();
x=ch-'0';ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*fu;
}
int G[55];
template<class T>inline void write(T x)
{
int g=0;
if(x<0) x=-x,putchar('-');
do{G[++g]=x%10;x/=10;}while(x);
for(int i=g;i>=1;--i)putchar('0'+G[i]);putchar('\n');
}
string str,t;
LL r[300010],pre[300010],suf[300010],sum[300010],ans;
void init()
{
memset(r,0,sizeof(r));
memset(pre,0,sizeof(pre));
memset(suf,0,sizeof(suf));
memset(sum,0,sizeof(sum));
t.clear();
t="$#";
for(int i=0;i<str.size();i++)
{
t+=str[i];
t+='#';
}
t+='@';
str=t;
}
void manacher()
{
int mx=1,p=1;
for(int i=1;i<str.size()-1;i++)
{
if(i<mx) r[i]=min((LL)mx-i,r[2*p-i]);
else r[i]=1;
while(str[i-r[i]]==str[i+r[i]]) r[i]++;
if(mx<i+r[i])
{
p=i;
mx=i+r[i];
}
}
}
int main()
{
while(cin>>str)
{
int len=str.size();
init();
// cout<<str<<endl;
manacher();
for(int i=2;i<=len*2;i++)
{
int x=(i+1)/2;
suf[x]++;
suf[x+r[i]/2]--;
}
for(int i=len*2;i>=2;i--)
{
int x=i/2;
pre[x]++;
pre[x-r[i]/2]--;
}
for(int i=len;i>=1;i--)
{
pre[i]+=pre[i+1];
}
for(int i=1;i<=len;i++)
{
suf[i]+=suf[i-1];
sum[i]=sum[i-1]+suf[i];
}
ans=0;
for(int i=1;i<=len;i++)
{
ans+=pre[i]*sum[i-1];
}
cout<<ans<<endl;
}
return 0;
}

双倍回文

题面亦可见洛谷 P4287 [SHOI2011]双倍回文

在跑manacher的更新答案时,同时看看当前回文串的前一部分的后缀是否也是一个回文串。此判定可以用之前计算过的\(r[i]\)判定。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<queue>
#include<vector>
#define IL inline
#define re register
#define LL long long
#define ULL unsigned long long
#ifdef TH
#define debug printf("Now is %d\n",__LINE__);
#else
#define debug
#endif
using namespace std; template<class T>inline void read(T&x)
{
char ch=getchar();
int fu;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') fu=-1,ch=getchar();
x=ch-'0';ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
x*=fu;
}
inline int read()
{
int x=0,fu=1;
char ch=getchar();
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') fu=-1,ch=getchar();
x=ch-'0';ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*fu;
}
int G[55];
template<class T>inline void write(T x)
{
int g=0;
if(x<0) x=-x,putchar('-');
do{G[++g]=x%10;x/=10;}while(x);
for(int i=g;i>=1;--i)putchar('0'+G[i]);putchar('\n');
}
int n;
int len;
char ch[1000010];
string t;
int r[1000010];
int p=0,mx=0,ans;
int main()
{
n=read();
cin>>t;
ch[len++]='$';
ch[len++]='#';
for(int i=0;i<t.size();i++)
{
ch[len++]=t[i];
ch[len++]='#';
}
ch[len]='\0';
// for(int i=0;i<=len;i++) cout<<ch[i];
for(int i=1;i<=len;i++)
{
r[i]=i<mx?min(mx-i,r[2*p-i]):1;
while(ch[i-r[i]]==ch[i+r[i]]) r[i]++;
if(i+r[i]>mx)
{
if(i&1)
{
for(int j=max(mx,i+4);j<i+r[i];j++)
{
if((j-i)%4==0&&r[(i-(j-i)/2)]>(j-i)/2) ans=max(ans,j-i);
}
}
mx=i+r[i];
p=i;
}
}
write(ans);
return 0;
}

在题解中也遇到了说可以用并查集,回文自动机(PAM)的,日后了解。

最长双回文串

很容易想到Manacher,先打一个板子。处理完以i为中心的最长回文串之后就不知道该做什么了。

想起例题,在维护\(r[i]\)的同时再维护\(suf[i]\)和\(pre[i]\)……仿佛这里有点意思。

在计算\(r[i]\)时同时维护\(ll[i],rr[i]\),分别表示以i为终点/起点的最长回文子串。

(然后其实这里可以用线段树维护,但会多一个\(\log\)||\(10^5\)谁会管多不多个\(\log\)呢||算了考虑各方面还是用Manacher吧)

首先在计算每一个\(r[i]\)时,只更新最长子序列的左右端点,即\(ll[i+r[i]-1],rr[i-r[i]+1]\)

注意到在原串中,相邻的回文子串,将变成,变换后的串中,以#为间隔的回文子串。

之后再扫一遍,看能否更新周围的\(ll\)和\(rr\)。

注意,这个更新是单向的。比如说\(rr[i]\)表示的是以\(i\)为起点的最长回文子串长度,那么就只能更新它右边的\(rr[i+2]\)。\(ll[i]\)同理。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<queue>
#include<vector>
#define IL inline
#define re register
#define LL long long
#define ULL unsigned long long
#ifdef TH
#define debug printf("Now is %d\n",__LINE__);
#else
#define debug
#endif
using namespace std; template<class T>inline void read(T&x)
{
char ch=getchar();
int fu;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') fu=-1,ch=getchar();
x=ch-'0';ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
x*=fu;
}
inline int read()
{
int x=0,fu=1;
char ch=getchar();
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') fu=-1,ch=getchar();
x=ch-'0';ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*fu;
}
int G[55];
template<class T>inline void write(T x)
{
int g=0;
if(x<0) x=-x,putchar('-');
do{G[++g]=x%10;x/=10;}while(x);
for(int i=g;i>=1;--i)putchar('0'+G[i]);putchar('\n');
}
int n;
char ch[500010];
int r[500010],ll[500010],rr[500010];
int main()
{
ch[0]='$';
scanf("%s",ch+1);
n=strlen(ch+1);
// cout<<n<<endl;
for(int i=n;i>=1;i--)
{
ch[i*2]=ch[i];
ch[i*2+1]='#';
}
ch[1]='#';
ch[2*n+2]='@';
for(int i=1,mx=1,p=1;i<=2*n+1;i++)
{
r[i]=i<mx?min(mx-i,r[2*p-i]):1;
while(ch[i+r[i]]==ch[i-r[i]]) r[i]++;
if(mx<i+r[i])
{
mx=i+r[i];
p=i;
}
ll[i+r[i]-1]=max(ll[i+r[i]-1],r[i]-1);
rr[i-r[i]+1]=max(rr[i-r[i]+1],r[i]-1);
}
for(int i=1;i<=2*n+1;i+=2)
{
rr[i+2]=max(rr[i+2],rr[i]-2);
}
for(int i=2*n+1;i>=3;i-=2)
{
ll[i-2]=max(ll[i-2],ll[i]-2);
}
int ans=0;
for(int i=1;i<=2*n+1;i+=2)
{
if(ll[i]&&rr[i]) ans=max(ans,ll[i]+rr[i]);
}
write(ans);
return 0;
}

注意第88行(倒数第5行)必须先判断ll和rr都有值,才能更新ans。

hack数据类似于awa,错误答案3,正确输出2。因为程序在计算第一个#(以及最后一个#)时,\(ll=3,rr=0\)。这种只有一边有回文串的端点不应被更新答案。

在luogu被hack,但是这里没有awa.

对称子序列

求一个只含ab的字符串有多少个非连续对称子序列

  1. 位置和字符都关于某条对称轴对称。

  2. 选取的子序列不能是连续的一段(即不能是一个子串)。

答案对\(10^9+7\)取模。

\(|S|\le 10^5\)。

首先明确一个公式,非连续对称子序列数=回文子序列数-回文子串数

回文子串数可以用Manacher求出。

考虑怎么求回文子序列数?

注意到用Manacher求出的是以每条对称轴所构成的最长回文子串的长度,其实是计算了每个对称轴对答案的贡献。

那么回文子序列数也可以用类似的思想。

设\(f[(i+j)/2]\)是以\(i,j\)中间点为对称轴,有多少对称的字符。

这个可以用\(FFT\)求。

类似于Manacher,可以通过插#把\((i+j)/2\)变成\(i+j\)。

那么这个对称轴受到的贡献就是\(2^{f[i]}-1\)。(每一对对称的字符都可以有选和不选两种情况,但是唯独有一种——一对都不选——不可以,所以要减去1)。

总的来说就是两遍\(FFT\)+快速幂+Manacher

Manacher例题问题汇总的更多相关文章

  1. 后缀数组&manachar总结

    洛谷题单 后缀数组 前置芝士 后缀数组 1 后缀数组 2 后缀数组 3 例题略解 P2463 [SDOI2008]Sandy的卡片 板子题... 然而我还是不会. 大概做法就是先把所有的串差分后拼成一 ...

  2. manacher(马拉车)算法详解+例题一道【bzoj3790】【神奇项链】

    [pixiv] https://www.pixiv.net/member_illust.php?mode=medium&illust_id=39091399 (CSDN好像有bug,不知道为什 ...

  3. 欧拉函数(汇总&例题)

    定义 欧拉函数 $\varphi(n)$表示小于等于$n$的正整数中与$n$互质的数的数目. 性质 1.积性函数(证明). 2.$\varphi(1)=1$(显然) 3.对于质数$n$,$\varph ...

  4. Manacher's Algorithm 马拉车算法

    这个马拉车算法Manacher‘s Algorithm是用来查找一个字符串的最长回文子串的线性方法,由一个叫Manacher的人在1975年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性,这 ...

  5. Manacher算法求回文半径

    http://wenku.baidu.com/link?url=WFI8QEEfzxng9jGCmWHoKn0JBuHNfhZ-tKTDMux34CeY8UNUwLVPeY5HA3TyoKU2XegX ...

  6. manacher最长回文子串

    https://www.luogu.org/blog/codesonic/manacheralgorithm 先放上洛谷的链接,毕竟讲的真好 两道例题 luogu4555 SP7586 inline ...

  7. yd的汇总

    因为是我这只蒟蒻个人的汇总嘛,可能有些奇♂怪的东西或者不规范的语言出现啦,见谅见谅 搬了一些到知识汇总里,删了一些过时和无用的,少了好多=.= 1.STL_queue 经实践验证,!qs.empty( ...

  8. 【算法】后缀自动机(SAM) 例题

    算法介绍见:http://www.cnblogs.com/Sakits/p/8232402.html 广义SAM资料:https://www.cnblogs.com/phile/p/4511571.h ...

  9. Fms3中client端与server端交互方式汇总

    系列文章导航 Flex,Fms3相关文章索引 Flex和Fms3打造在线聊天室(利用NetConnection对象和SharedObject对象) Fms3和Flex打造在线视频录制和回放 Fms3和 ...

  10. 学习笔记 - Manacher算法

    Manacher算法 - 学习笔记 是从最近Codeforces的一场比赛了解到这个算法的~ 非常新奇,毕竟是第一次听说 \(O(n)\) 的回文串算法 我在 vjudge 上开了一个[练习],有兴趣 ...

随机推荐

  1. Ai 文本生成式大模型 基础知识

    提示工程-RAG-微调 工程当中也是这个次序 提示词工程 RAG 微调 先问好问题 再补充知识 最后微调模型 RAG相关技术细节 选择合适的 Chunk 大小对 RAG 流程至关重要. Chunk 过 ...

  2. wxFormBuilder 代码运行报错,尝试删除报错代码部分语句

    解决方法: 定位到第60行,删掉部分代码如下 bSizer1.Add(gbSizer1, 1, wx.EXPAND , 1) 运行后效果如下图:

  3. MySQL-排序相关原理分析

    全字段排序和rowId排序 建表语句如下: CREATE TABLE `t` ( `id` int(11) NOT NULL, `city` varchar(16) NOT NULL, `name` ...

  4. study Rust-3【表达式和函数】

    1. Rust与优美的pascal很相似.但是这个表达式概念很有意思.见上图.[1.条件赋值语句:2.表达式返回值] 2.注意变量和隐藏变量的概念,这个也有创意. 3.函数在Rust无处不在.

  5. 掌握FastAPI与Pydantic的跨字段验证技巧

    title: 掌握FastAPI与Pydantic的跨字段验证技巧 date: 2025/04/01 00:32:07 updated: 2025/04/01 00:32:07 author: cmd ...

  6. 用99元买的服务器搭一套CI/CD系统

    故事的开始是这样的:无聊的时候在阿里云买了一个99/年的服务,上面部署了一个Git服务,用于托管自己无聊时写的一些代码,顺便也拿它做开发服务器.为了方便应用管理,起初用docker来管理和部署应用,后 ...

  7. DevSecOps的实现与相关开源工具

    DevSecOps的实现与相关开源工具 DevSecOps是一种以自动化方式在DevOps流程中集成安全工具的方法.DevSecOps不仅仅是引入新的安全工具,还包括关于使用这些工具的必要知识.这需要 ...

  8. 在Linux终端管理你的密码!

    大家好,我是良许. 现在是互联网时代,我们每天都要跟各种 APP .网站打交道,而这些东西基本上都需要注册才可以使用. 但是账号一多,我们自己都经常记不清对应的密码了.有些小伙伴就一把梭,所有的账号密 ...

  9. CAS前后端分离解决方案

    CAS前后端分离解决方案 关于CSS服务器的搭建和整合SpringBoot参考:CAS5.3服务器搭建与客户端整合SpringBoot以及踩坑笔记 环境与需求 后端:springboot 前端: vu ...

  10. Windows IntelliJ IDEA 快捷键终极大全

    自动代码 常用的有fori/sout/psvm+Tab即可生成循环.System.out.main方法等boilerplate样板代码 . 例如要输入for(User user : users)只需输 ...