manacher(马拉车)算法详解+例题一道【bzoj3790】【神奇项链】
来源:互联网 发布:网络推广费用预算 编辑:程序博客网 时间:2024/06/06 09:15
[pixiv] https://www.pixiv.net/member_illust.php?mode=medium&illust_id=39091399
(CSDN好像有bug,不知道为什么存的草稿覆盖了之前的博客>.<,以后再也不存线上草稿了)
昨天学了马拉车(manacher)算法,今天做了一道例题。虽然并不难,但还是写一写博客,即为民服务,又加深自己的理解。
manacher是高效处理回文串的算法,不过因为只限求回文串,所以适用范围就窄了,但是它仍然有用,所以还是要学。
首先是一个小小的处理技巧。因为回文串可能是奇数可能是偶数,偶数的时候就没有中心字符了,没有著脚点可恼火了。所以我们考虑用没有出现过的字符“#”来表示原串的间隔。eg。ababa->#a#b#a#b#a#。为了避免溢出,访问到不存在的点(’\0’=’\0’),我们再在首尾加上没有出现过的不同字符,eg’+’,’-‘。显然,现在对新串的每一个字符找以此字符为中心的字符串即可。
manacher的思想就是从左到右求出以每个位置为中心的最长字符串(由于回文串很明显的包含和对称性质,以同一位置为中心的字符串自然是包含在最长回文串里),利用回文串的对称性质,对每一个位置利用之前的信息来快速得到答案。近似于扫一遍,所以均摊o(n)(之后详细证明)
那么之前的信息是什么呢?我们记向右延伸最远的回文串(有多个的话,选中心点最靠左的),令其右端点为mx,中心点的下标为id。我们需要处理数组pal[i],表示以i为中心的回文串的右端到i的长度。
下面就是怎么实现的问题了。
对于一个位置i,有三种情况
1、i<=mx,我们可以由对称性得出其关于id的对称点的pal[]
此时易得pal[i]=pal[2*id-i];
2、同样的情况,i<=mx,但是其对应点的范围超出了id的范围。
此时可得pal[i]>=mx-i+1;
由对称性可得,在id范围内的部分i可由2*id-i推来,但外面的部分必定与另一边不同(想想),暴力扩充即可,将mx更新。暴力判断pal[i]能到多少,每次判断成功都等价于mx++,失败则结束。由于mx是单增的,于是判断成功的次数不超过串长次,均摊o(n)
3、i>mx。处理方法与2相同。
放个代码更好理解
void manacher(){ int mx=0,id; for(int i=1;i<=m;i++){ if(mx>=i) pal[i]=min(mx-i+1,pal[2*id-i]); else pal[i]=1; while(s[i-pal[i]]==s[i+pal[i]]) pal[i]++; if(i+pal[[i]-1>mx) mx=i+pal[i]-1,id=i; } }}
由代码可以也可以看出,mx是单增的,均摊o(n)
此外,manacher算法还引出了一个——
极重要的性质
再来看这张图,我们发现,如果mx不更新,就不会出现本质不同的回文子串,因为前面已经出现过了;而每扩展一次mx,最多新出现一个本质不同的回文子串。
于是得到性质:#一个字符串最多只有n个本质不同的回文子串#。这个性质很重要,有些题会用到,需要这个性质去分析。
基础讲完了,终于可以放题了:D
Description
母亲节就要到了,小 H 准备送给她一个特殊的项链。这个项链可以看作一个用小写字
母组成的字符串,每个小写字母表示一种颜色。为了制作这个项链,小 H 购买了两个机器。第一个机器可以生成所有形式的回文串,第二个机器可以把两个回文串连接起来,而且第二个机器还有一个特殊的性质:假如一个字符串的后缀和一个字符串的前缀是完全相同的,那么可以将这个重复部分重叠。例如:aba和aca连接起来,可以生成串abaaca或 abaca。现在给出目标项链的样式,询问你需要使用第二个机器多少次才能生成这个特殊的项链。
Input
输入数据有多行,每行一个字符串,表示目标项链的样式。
Output
多行,每行一个答案表示最少需要使用第二个机器的次数。
Sample Input
abcdcba
abacada
abcdef
Sample Output
0
2
5
HINT
每个测试数据,输入不超过 5行
每行的字符串长度小于等于 50000
先用manacher跑出pal[ ]数组,因为题目说可以重合,于是就转换成了经典的区间覆盖问题,贪心即可。
代码
#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int N=50000+5;int len,pal[N*2];char s[N],a[N*2];struct Node{ int le,ri;}qu[N*2];bool cmp(Node a,Node b){ return a.le<b.le;}void insert(){ memset(a,0,sizeof(a)); int lena=-1; a[++lena]='+'; for(int i=0;i<len;i++){ a[++lena]='#'; a[++lena]=s[i]; } a[++lena]='#'; a[++lena]='-'; len=lena-1;}void manacher(){ int mx=0,id; for(int i=1;i<=len;i++){ if(mx>=i) pal[i]=min(mx-i+1,pal[2*id-i]); else pal[i]=1; while(a[i-pal[i]]==a[i+pal[i]]) ++pal[i]; if(mx<i+pal[i]-1) mx=i+pal[i]-1,id=i; }}int fugai(){ int ans=0,far=1; int i=1; for(i=1;qu[i].le<=1;i++) if(qu[i].ri>qu[far].ri) far=i; while(i<=len){ ans++; int tmp=far; for(;qu[i].le<=qu[far].ri;i++) if(qu[i].ri>qu[tmp].ri) tmp=i; far=tmp; } return ans;}int main(){ while(scanf("%s",s)!=EOF){ len=strlen(s); insert(); manacher(); memset(qu,0,sizeof(qu)); for(int i=1;i<=len;i++) qu[i].le=i-pal[i]+1,qu[i].ri=i+pal[i]-1; sort(qu+1,qu+len+1,cmp); printf("%d\n",fugai()-1); } return 0;}
- manacher(马拉车)算法详解+例题一道【bzoj3790】【神奇项链】
- BZOJ3790 神奇项链(马拉车+BIT讲解)
- [bzoj3790]神奇项链 manacher
- [BZOJ3790]神奇项链(manacher+贪心)
- 【bzoj3790】【神奇项链】【manacher+dp+树状数组】
- Bzoj3790:神奇项链:manacher+线段树+贪心
- 马拉车算法(Manacher)详解
- Manacher(马拉车)算法详解
- [BZOJ3790] 神奇项链
- bzoj3790 神奇项链
- BZOJ3790 神奇项链
- [BZOJ3790]神奇项链-manachar
- bzoj3790 神奇项链
- 浅谈Manacher(马拉车)算法
- Manacher马拉车算法总结
- BZOJ3790 神奇项链 解题报告【字符串】【Manacher】【树状数组】【数据结构优化DP】
- acm--manacher(马拉车算法)(HDU 3294)
- 马拉车(Manacher)算法最通俗教学
- Java基础笔记整理---【07】面向对象程序设计-类和对象
- C语言:使用strtok()函数分割字符串
- 实现对一个8比特位数据(unsigned char)的指定位的置0或者置1操作
- QT样式使用
- UVA11806(容斥)
- manacher(马拉车)算法详解+例题一道【bzoj3790】【神奇项链】
- (新) Kotlin搞起来 —— 5.泛型
- Js常用的内置对象
- oracle的sqlplus设置显示行数和列数
- Box-sizing 中的content-sizing 和 border-sizing
- 顺时针打印矩阵
- Iterator迭代器
- jsonp 跨域请求
- 一款专业的Mac卸载清理工具