zoukankan      html  css  js  c++  java
  • manacher算法学习

    在一个长度为n的字符串中,找到这个字符串的最长回文长度。

    1、n^3做法

         枚举回文串的左端点和右端点,从左端点向右端点扫一遍,判断是否是回文。

    2、n^2做法

         很明显上面的做法浪费了许多判断的时间,若回文串长度是奇数,可以枚举回文串的中点mid,同时向两边扫,找到最长的回文长度,这样可以优化一个n的时间。

         若长度是偶数,就找到枚举相邻的两个字符,同时向两边扩展。

    3、n做法

         那如果n的大小很大,n^2的算法行不通呢?这时就要请manacher出马了。

         考虑如何把n^2算法变为n。在枚举回文串的中点时,我们判断了回文串的长度的奇偶性,这样就浪费了时间,且十分麻烦。

         所以可以在每个字符串中加上一个原字符串中不会出现的字符(如‘#’),这样就可以将每一个回文串的长度变为奇数。代码如下:

    1 void prepare(void) {
    2     s[n << 1] = '#';
    3       for (int i = n; i ; -- i) {
    4         s[i * 2 - 1] = s[i];
    5         s[i * 2 - 2] = '#';
    6       } 
    7     n <<= 1;
    8 }

         可这还是无法将n^2的算法优化成n。会发现,在枚举每个点为中点时,都要向枚举向两边扩展的长度,这样会浪费许多时间,那么如何利用之前的判断快速进行本次的判断呢。

         可以记录下maxright表示之前的扩展可以达到的最右边位置,mid表示maxright所对应的回文串的中心,hw[i]表示以i为回文中心所能扩展出去的最长长度(包括i)。

         在枚举中点i时,若i < maxright,如下图(mr是maxright,l是maxright关于mid对称的点,j是i点关于mid对称的点):

        因为j点与i点对称,所以i点的hw值应和j点相同。可是若j点的hw值大于了mr - i,那么便不可以直接进行转移(因为这时i与j不再对称),要暴力计算出余下部分是否对称。

        若i > maxright,那么就暴力计算hw[i],后对maxright和mid进行更新。代码如下:

    void manacher(void) {
        prepare();
        maxright = 0; mid = 0; ans = 0;
          for (int i = 0; i <= n; ++ i) {
              if (i < maxright) { //取出相同的部分 
                  hw[i] = min(hw[mid * 2 - i], maxright - i);
              }
            else hw[i] = 1;
            while (0 <= i - hw[i] && i + hw[i] <= n && s[i + hw[i]] == s[i - hw[i]]) hw[i] ++; //暴力计算hw值 
            if (i + hw[i] > maxright) { //更新maxright和mid 
              maxright = i + hw[i] - 1;
              mid = i;
            }
          } 
    }

    放一道模板题:hdu3068  http://acm.hdu.edu.cn/showproblem.php?pid=3068

    代码如下:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    const int maxn = 110005;
    int hw[maxn << 1],maxright,mid,ans,n;
    char s[maxn << 1];
    
    void prepare(void) {
        s[n << 1] = '#';
          for (int i = n; i ; -- i) {
            s[i * 2 - 1] = s[i];
            s[i * 2 - 2] = '#';
          } 
        n <<= 1;
    }
    
    void manacher(void) {
        prepare();
        maxright = 0; mid = 0; ans = 0;
          for (int i = 0; i <= n; ++ i) {
              if (i < maxright) { 
                  hw[i] = min(hw[mid * 2 - i], maxright - i);
              }
            else hw[i] = 1;
            while (0 <= i - hw[i] && i + hw[i] <= n && s[i + hw[i]] == s[i - hw[i]]) hw[i] ++;
            if (i + hw[i] - 1 > maxright) { 
              maxright = i + hw[i] - 1;
              mid = i;
            }
          } 
          for (int i = 1; i <= n; ++i) 
            ans = max(ans, hw[i] - 1);
    }
    
    int main() {
        while (scanf("%s",s + 1) != EOF) {
          memset(hw, 0, sizeof(hw));
          n = strlen(s + 1);
          manacher();
          printf("%d
    ", ans);
        }
        return 0;
    } 
  • 相关阅读:
    Java多线程
    http网页请求状态码
    C++文件读写
    算法训练 最大的算式
    算法训练 2的次幂表示
    线段树- 算法训练 操作格子
    Prim算法(最小生成树)
    Kruskal算法(最小生成树)
    Dijkstra算法(最短路)
    HDU5692 dfs + 线段树维护区间最大值
  • 原文地址:https://www.cnblogs.com/Gaxc/p/9874673.html
Copyright © 2011-2022 走看看