Manacher例题问题汇总
Manacher例题问题汇总
本篇随笔面向个人
本来以为回文串很简单,但是没有做对应的练习前下此定论为时过早。
https://www.ybtoj.com.cn/contest/75
模板
虽然例题中也没有模板题(因为太简短了……),但是有必要预先打一遍。
步骤
将原字符串头部插入
$
,尾部插入@
或\0
,再将间隔中插入#
(包括$
后和@
前也要有#
);比如说如果原串为abc
,则变换为$#a#b#c#@
。定义变量mx,p,r[i]。
遍历新字符串。首先确定当前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;
}
双倍回文
在跑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.
对称子序列
求一个只含a
和b
的字符串有多少个非连续对称子序列。
位置和字符都关于某条对称轴对称。
选取的子序列不能是连续的一段(即不能是一个子串)。
答案对\(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例题问题汇总的更多相关文章
- 后缀数组&manachar总结
洛谷题单 后缀数组 前置芝士 后缀数组 1 后缀数组 2 后缀数组 3 例题略解 P2463 [SDOI2008]Sandy的卡片 板子题... 然而我还是不会. 大概做法就是先把所有的串差分后拼成一 ...
- manacher(马拉车)算法详解+例题一道【bzoj3790】【神奇项链】
[pixiv] https://www.pixiv.net/member_illust.php?mode=medium&illust_id=39091399 (CSDN好像有bug,不知道为什 ...
- 欧拉函数(汇总&例题)
定义 欧拉函数 $\varphi(n)$表示小于等于$n$的正整数中与$n$互质的数的数目. 性质 1.积性函数(证明). 2.$\varphi(1)=1$(显然) 3.对于质数$n$,$\varph ...
- Manacher's Algorithm 马拉车算法
这个马拉车算法Manacher‘s Algorithm是用来查找一个字符串的最长回文子串的线性方法,由一个叫Manacher的人在1975年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性,这 ...
- Manacher算法求回文半径
http://wenku.baidu.com/link?url=WFI8QEEfzxng9jGCmWHoKn0JBuHNfhZ-tKTDMux34CeY8UNUwLVPeY5HA3TyoKU2XegX ...
- manacher最长回文子串
https://www.luogu.org/blog/codesonic/manacheralgorithm 先放上洛谷的链接,毕竟讲的真好 两道例题 luogu4555 SP7586 inline ...
- yd的汇总
因为是我这只蒟蒻个人的汇总嘛,可能有些奇♂怪的东西或者不规范的语言出现啦,见谅见谅 搬了一些到知识汇总里,删了一些过时和无用的,少了好多=.= 1.STL_queue 经实践验证,!qs.empty( ...
- 【算法】后缀自动机(SAM) 例题
算法介绍见:http://www.cnblogs.com/Sakits/p/8232402.html 广义SAM资料:https://www.cnblogs.com/phile/p/4511571.h ...
- Fms3中client端与server端交互方式汇总
系列文章导航 Flex,Fms3相关文章索引 Flex和Fms3打造在线聊天室(利用NetConnection对象和SharedObject对象) Fms3和Flex打造在线视频录制和回放 Fms3和 ...
- 学习笔记 - Manacher算法
Manacher算法 - 学习笔记 是从最近Codeforces的一场比赛了解到这个算法的~ 非常新奇,毕竟是第一次听说 \(O(n)\) 的回文串算法 我在 vjudge 上开了一个[练习],有兴趣 ...
随机推荐
- c++中的类成员函数指针
c++中的类成员函数指针 文章目录 c++中的类成员函数指针 发生的事情 正常的函数指针定义 定义类的成员函数指针 std::function 发生的事情 最近,想用一个QMap来创建字符串和一个函数 ...
- go 编译约束//go:build dev //+build
前言 在真实环境中,我们可能需要为不同的编译环境编写不同的 Go 代码,所以需要做构建约束. 比如:syscall.NewLazyDLL("test.dll") 加载 dll 的程 ...
- Go语言修改字符串
Go 语言的字符串无法直接修改每一个字符元素,只能通过重新构造新的字符串并赋值给原来的字符串变量实现.请参考下面的代码: angel := "Heros never die" an ...
- oracle调整sga、pga大小
展开修改sga大小1-1查看当前sga大小SQL> show parameter sga1-2修改sga_max_size为24GSQL> alter system set sga_max ...
- 《视觉SLAM十四讲》第13讲 设计SLAM系统 回环检测线程的实现
<视觉SLAM十四讲>第13讲 设计SLAM系统 回环检测线程的实现 这个学期看完了高翔老师的<视觉SLAM十四讲>,学到了很多,首先是对计算机视觉的基本知识有了一个更加全面系 ...
- 包装类面试题--java进阶day05
1.面试题 如下两个输出,请问分别是true还是false呢? 答案: 当范围在-128~127时,对象相同就会返回true 在讲解这个问题之前,先了解自动装箱的原理 2.自动装箱的原理 自动装箱,就 ...
- 免费包白嫖最新DeepSeek-V3驱动的MCP与SemanticKernel实战教程 - 打造智能应用的终极指南
如果您需要深入交流了解请加入我们一块交流 https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=b7co0430-d ...
- fiddler断点应用
一.作用 1.模拟网络中断 2.断点时篡改数据 3.测试时做一些极端测试 二.断点步骤 1.全局断点 1)全局断点的两种方式 点击状态栏空白框,点击一下请求前断点,两下请求后断点,三下取消断点 Rul ...
- 数据结构之位图(bitmap、RoaringMap)
参照资料: 1.https://www.bilibili.com/video/BV1u44y1g7Ps(bitmap) 2.https://b23.tv/cQtuFOx (RoaringMap) 3. ...
- [Ubuntu 20.04] 修复‘systemd-shutdown[1]: waiting for process: crond’需等待1分半钟的问题
由于在2020-2021年期间下载过Linux版本的Free Download Manager(简称FDM,一款免费但不开源的跨平台下载工具),而该软件的官网被挂了木马,因此在此期间下载安装过FDM的 ...