zoukankan      html  css  js  c++  java
  • 20201011day32 复习5:Manacher、KMP

    KMP算法

    回文串问题

    查找一个字符串中的最长回文子串

    暴力算法

    对于每个字符串,枚举每个位置并向两边扩展,看是否回文。

    缺点:1.无法处理偶数长度串 2.平均时间复杂度(O(n^2))

    manacher算法

    1. manacher对字符串的预处理
      对于偶数长度字符串的预处理,使得它变成奇数长度字符串,即在字符串的首位加上一个特定字符,如aa变成#a#a#,这使得原本长度为(n)的字符串变为(2n+1),保证是奇数。
      这里解释一下为什么预处理后不会影响对字符串的扩展匹配。比如我们的原字符串是 a,假设预处理后的字符串是 #a#a#,我们在任意一个点,比如字符#,向两端匹配只会出现a 匹配 a# 匹配 # 的情况,不会出现原字符串字符与特殊字符匹配的情况,这样就能保证我们不会改变原字符串的匹配规则。通过这个例子,你也可以发现实际得到的结果与上述符合。

    补充:首尾可以放置不同的字符,如^&,这样可以自动判断跳出循环,而不用担心越界
    2. 说明
    (1)对于一个回文串,有且仅有一个对称中心,叫做回文对称中心。
    (2)在一个回文串内,任选一段区间(X),一定存在关于“回文对称中心”对称的一个区间,把这个区间叫做关于区间(X)的对称区间。区间和对称区间是全等的。
    (3)如果一个区间的对称区间是回文串,那么这个区间也一定是一个回文串。在大的回文串内,它们的回文串半径相等。
    (4)我们通过确定关系预先得到的回文半径,它的数值必定小于等于这个位置真实的回文串半径。

    因此,我们如果可以记录以每个位置为中心的回文串半径,当我们通过另一个回文中心将这个原先的中心点对称过去的时候,就可以确定对称过去的那个点的回文半径了。

    考虑另一个回文中心如何确定,就是那个极大回文串的回文中心,也就是边界顶着右边我们已知的最远位置的,最长的回文串。

    然而考虑到,我们只能确认我们已知的回文串内的对称关系和回文半径等量关系,关于这个极大回文串右侧,我们啥也不知道。

    记录这些数据到p数组,同时记录一个mid,一个r,分别代表已经确定的右侧最靠右的回文串的对称中心和右边界。

    那么我们扫描到一个新的字符时候,怎么先确定它的部分回文半径呢?若当前扫描到的位置为i,(midle ile r),我们就可以找到他的一个对称点,位置是(2 imes mid-i)

    所以,拓展一个新点时,我们不必从这个点左右两边第一个位置开始向两边拓展,可以预先确定一部分回文串。所以可以看出字符串的线性复杂度。

    1. 若扩展一个新的关于该字符的回文半径,可以先确定一部分p[i]。
    2. 我们知道我们能确定的范围,其右侧不得大于r,即(p_i+i-1le r),移项得(p_ile r-i+1)

    取一个min,所以p[i]=min(p[mid*2-i],r-i+1);,最终答案是max(p[i])-1

    code

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int maxn=11000002;
    char data[maxn<<1];
    int p[maxn<<1],cnt,ans;
    inline void qr(){
          char c=getchar();
          data[0]='~',data[cnt=1]='|';
          while(c<'a'||c>'z') c=getchar();
          while(c>='a'&&c<='z') data[++cnt]=c,data[++cnt]='|',c=getchar();
    }
    int main(){
          qr();//优化后的读入
          for(int t=1,r=0,mid=0;t<=cnt;++t){
    	    if(t<=r) p[t]=min(p[(mid<<1)-t],r-t+1);
    	    while(data[t-p[t]]==data[t+p[t]]) ++p[t];
    	    //暴力拓展左右两侧,当t-p[t]==0时,由于data[0]是'~',自动停止。故不会下标溢出。
    	    //假若我这里成功暴力扩展了,就意味着到时候r会单调递增,所以manacher是线性的。
    	    if(p[t]+t>r) r=p[t]+t-1,mid=t;
    	    //更新mid和r,保持r是最右的才能保证我们提前确定的部分回文半径尽量多。
    	    if(p[t]>ans) ans=p[t];
          }
          printf("%d
    ",ans-1);
          return 0;
    }
    
    
  • 相关阅读:
    测试框架 MSTest V2与单元测试
    string字符串格式
    重构概述
    代码的坏味道
    this.Dispatcher.Invoke与SynchronizationContext
    C# new关键字
    Servlet的API和生命周期
    Servlet快速入门
    Spring介绍
    Oracle数据安全解决方案(1)——透明数据加密TDE
  • 原文地址:https://www.cnblogs.com/liuziwen0224/p/20201011day32-001.html
Copyright © 2011-2022 走看看