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 上开了一个[练习],有兴趣 ...
随机推荐
- Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
引言:AI技术新纪元的破局者 2025年3月6日凌晨,武汉Monica团队正式发布全球首款通用AI代理系统Manus,该工具在GitHub开源社区引发热议,单日Star数突破5万.与传统对话式AI不同 ...
- abaqus&FEA资料-科研&工具-导航
复合材料力学 BLOGs上的书籍共享文件夹 2004-Mechanics of Composite Structural Elements.pdf,onedrive link Mechanics Of ...
- mysql安装以及2059 - Authentication plugin 'caching_sha2_password' cannot be loaded:报错的解决办法
2059 - Authentication plugin 'caching_sha2_password' cannot be loaded: dlopen(../Frameworks/caching_ ...
- go context 子Goroutine超时控制
context使用 Go语言第一形参通常都为context.Context类型,1. 传递上下文 2. 控制子Goroutine超时退出 3. 控制子Goroutine定时退出 package mai ...
- study Python3【2】导入模块
import 与 from...import 在 python 用 import 或者 from...import 来导入相应的模块. 将整个模块(somemodule)导入,格式为: import ...
- Linux | 如何创建一个 home 目录在 /data 磁盘的 sudo 用户
需求: 拿到了 boss 的服务器账号 ssh boss@172.16.1.100,需要登录 boss 的账号,然后为自己创建一个账号,实现 ssh <user_name>@172.16. ...
- adb环境配置笔记
adb环境配置不需要先配置好jdk,然后配置adb环境,才能命令行运行adb https://blog.csdn.net/shengmer/article/details/79027828 https ...
- 🎀Nginx 安全设置(禁止Iframe跨域访问、隐藏server、限制ip访问)
1.安装[headers-more-nginx-module]模块,自定义nginx头信息 2.禁止Iframe跨域请求 more_set_headers 'X-Frame-Options SAMEO ...
- 使用PyMuPDF对pdf文件插入文字时 遇到配置本地的字体文件缺仍然使用默认Helvetica字体问题
背景 昨天收到的新需求,一份文件从其他部门发起,进行一些文字填写后盖章,再到我们部门,我们接收到的是pdf文件,所以需要在pdf文件中进行修改,插入当日日期等文字.但有要求字体必须和原文档字体相同. ...
- Java 的 G1 垃圾回收流程
Java 的 G1 垃圾回收流程 G1(Garbage-First)垃圾收集器 是一种区域化.并发.低延迟的垃圾回收器,适合大堆内存和对暂停时间有严格要求的应用程序.G1 的垃圾回收流程主要包括以下阶 ...