<学习笔记>manacher算法

来源:互联网 发布:淘宝买欢乐豆 编辑:程序博客网 时间:2024/06/10 10:45

我是吐槽,你看不到我~~ (*/ω\*) 清北学堂学习第七天来了个特别漂亮的小姐姐(syq)讲了字符串算法,如 kmp,manacher,hash…还特别安利说manacher可以O(n)复杂度解决回文子串问题233 上课只顾着看小姐姐了。下午考试她说有上午学的一部分内容,说帮我们巩固一下。道理我都懂,可是上来第一题就是manacher算法是要闹那样啊,内流满面T^T。。。我上午只看了看思想啊QAQ。。。一定要把模板保存下来。。。

回文串问题

最长回文子串问题:给定一个字符串,求它的最长回文子串长度(n<=10^7)

*回文串:正序和逆序相等的串。从中心轴出发左右对称,中心轴可以是一个字符,也可以是两个字符间的一个间隙。

暴力吗?
枚举每个子串暴力判断…n^3 !!
那优化一下?
枚举中心轴看最长扩展长度。
那字符间隙呢?
在字符间隙里再插上一个#就好了… n^2!(保证#在原串中没有出现过)
还能优化吗?
发现形如 ababababa 发现枚举第3个b时的回文串在枚举第三个a时已经被匹配过一遍了,考虑能不能像kmp算法一样利用之前的匹配信息?

答案是可以的!

Manacher算法

在O(n)时间内,计算出一个字符串以每个位置为中心轴(一个字符或者字符间隙)最长能扩展出多长的回文串。

预处理:将所有的字符都转为奇数长度。
aba–> #a#b#a#
abba–>#a#b#b#a#

另,设r[i]为以i为中心轴的最长匹配半径(不包括i)

栗子:

# a # b # a #
0 1 0 3 0 1 0
# a # b # b # a #
0 1 0 1 4 1 0 1 0

发现r[i]恰好就是以i为中心的最长回文串长度。

能否快速求出r?

定义两个变量:

pos=一个当前向右延伸的位置最远的回文串的中心轴。
maxn_r=这个中心轴的r。

这里写图片描述

前提:i>pos

计算r[i]时分三种情况讨论:

① i < maxn_r 时: 取i关于pos的对称点j
由回文串性质,可知
⑴ 若r[j]+i<=maxn_r , r[i]=r[j];

这里写图片描述

⑵若r[j]+i>maxn_r,
可知 i~maxn_r一段一定是回文串。
从maxn_r+1开始逐位匹配并更新maxn _r和pos。
maxn_r=i+r[i],pos=i;

这里写图片描述

② i>=maxn_r时 从i往右开始逐位匹配,maxn _r=i+r[i],pos=i;

这里写图片描述

代码:

void Manacher(){    l=strlen(b);    for(int i=0;i<l;++i)    {        a[i<<1]='#';        a[i<<1|1]=b[i];    }    l=l*2+1;    a[l-1]='#';    maxn_r=0,pos=0;    for(int i=0;i<l;++i)    {        if(maxn_r>i) r[i]=min(maxn_r-i,r[2*pos-i]);        else r[i]=0;        while(i+r[i]+1<l&&i-r[i]-1>=0&&a[i+r[i]+1]==a[i-r[i]-1]) r[i]++;        if(r[i]+i>maxn_r)         {            maxn_r=r[i]+i;            pos=i;         }    }}

一道简单题

题目描述

小 A 是个学渣,很快就要到 3000 年 NOIprofessional 初赛了,然而他什么题都不会写。
于是他开始研究往年卷的选项分布(然并卵)。
小 A 有 M 份试题。 由于是 professional 版本的考试,初赛试题题目数量 N 非常大。 小
A 发现,某些年份的答案序列里有很多长度超过 k 的回文子串。 极长回文子串 str 定义
为以答案序列 s 中不存在与 str 同对称轴的更长回文子串。 小 A 想知道,每个年份的答
案序列里有多少长度超过 k 的极长回文子串。

输入格式

第一行两个数字 M,k,表示试卷数和长度下限 k
接下来 M 行,每行一个正整数和一个字符串,表示试题的年份和答案,答案序列不区
分大小写。

输出格式

输出共 M 行,每行两个数,第一个数为年份,第二个数为长度超过 k 的极长回文子串
的数量。按照数量递减,数量相同时年份递增的顺序输出。

样例输入

5 3
2011 bacddcb
2000 cacac
2012 aaacdadd
2008 dcdaa
1999 abcda

样例输出

2000 3
2012 2
2008 1
2011 1
1999 0

格式要求

输入文件 Xuezha.in
输出文件 Xuezha.out
时间限制:1s 内存限制:256MB
数据范围
年份 Y,2000<=Y<3000
对于 30%的数据,1<=M<=10,1<=N<=100,1<=k<=100
对于 100%的数据,1<=M<=100,1<=k<=100000,1<=N<=100000

模板题呦φ(>ω<*)

特别注意!!!题目里是不区分大小写的!!!就是说数据里有大写!!就这一点全场wa了一片,没几个有分的QAQ。 wa的一声就哭了。。。

#include<iostream>#include<cstdio>#include<cstring>#include<cmath>#include<algorithm>using namespace std;int maxn_r,pos,l,k,K,M;int r[200050];char a[200050],b[100050];struct maple{    int year;    long long num;}Paper[120];bool cmp(maple a,maple b){    if(a.num==b.num) return a.year<b.year;    else return a.num>b.num;}void Done(){    l=strlen(b);    for(int i=0;i<l;++i)    {        a[i<<1]='#';        if(b[i]>='A'&&b[i]<='Z') a[i<<1|1]=b[i]-'A'+'a';  // 单独写会T , mengbi        else a[i<<1|1]=b[i];    }    l=l*2+1;    a[l-1]='#';    maxn_r=0,pos=0;    for(int i=0;i<l;++i)    {        if(maxn_r>i) r[i]=min(maxn_r-i,r[2*pos-i]);        else r[i]=0;        while(i+r[i]+1<l&&i-r[i]-1>=0&&a[i+r[i]+1]==a[i-r[i]-1]) r[i]++;        if(r[i]+i>maxn_r)         {            maxn_r=r[i]+i;            pos=i;         }    }}int main(){    freopen("Xuezha.in","r",stdin);    freopen("Xuezha.out","w",stdout);    scanf("%d%d",&M,&K);    for(int i=1;i<=M;++i)    {        scanf("%d",&Paper[i].year);        Paper[i].num=0;        scanf("%s",b);  // 这里用cin,cout也会T        Done();        for(int j=0;j<l;++j)           if(r[j]>=K) ++Paper[i].num;    }    sort(Paper+1,Paper+M+1,cmp);    for(int i=1;i<=M;++i)    {        printf("%d %lld\n",Paper[i].year,Paper[i].num);    }    return 0;}
原创粉丝点击