• Manacher算法


    $$Manacher$$

    【杂言】 :

    任何的字符串算法在刚进行的时候,都会感觉很有难度, 唯一解决难度的问题, 就是多打多练,例如(Manacher),自然,也是有大佬直接一眼就可以看懂的,这也是我的一点理解


    【准备】:

    这也大多数的回文字符串可能用的,单独列一下。

    1. 首先在字符串的每一个字符之前或者之后都加入一个#,举个例子 :例如字符串(S = "abcd") , 根据这个意思,我们也就是要把他变成 (S=) "#a#b#c#d#" 至于为什么这么做?因为在进行回文字符串的判定时 ,字符串长度是偶数或奇数都会导致有不同的实现 , 十分的麻烦,同时,相比之下, 长度为奇数的 回文字符串更好判定, 所以转化为上述形式。关于一定会形成奇数的证明(相当于字符串 ( imes 2 +) #)。
    2. 为了防止每一次进行询问的时候都看一下是否会越界,我们在首尾添加上一个^和~,这种东西,当询问到那里的时候自动跳出就行了
    3. 但同时我们插入的这些所有的 字符,都必须保证和原字符保持互异 。
      综上 , 也就是把上面的字符串 ,改成了(S= ") ~#a#b#c#d#^(") 的这么一个基操,其实在普通的回文字符串的判定,基本上也会用到来优化代码;

    【算法】:

    通过举出一个例子来进行说明一下算法的流程;
    例子 : (S="abbahopo") ,我们发现在本串(S)中存在两个回文子串, 一个是长度为偶数的子串("abba"),和长度为奇数的回文子串 ,("opo"),我们按照上方的准备,魔改一下,就出来了新的(S)(仍旧选用(S),应该是(S_{new})的),= " ~ #a#b#b#a#h#o#p#o# ^ ",经过魔改之后,其长度也就都是奇数了,然后再来考虑一下其寻找过程。

    由于下面插入在使用(Markdown)时,用,#会导致语法异常,在这里省略掉开头和结尾的^,并且用(1)来代替#。

    定义数组 (len_i) ,用于保存字符(i)的最大扩展长度,也就是回文半径, 同时 , 它也是去掉我们加的#字符的原数组的该扩展的字符串的总长度,例如下图中#o#p#o#,其最大回文半径就是(3),并且,该字符串的长度也就是(3)

    [egin{array}{c|lcr} string.len& ext{1}& ext{2}& ext{3}& ext{4} & ext{5}& ext{6}& ext{7}& ext{8}& ext{9}& ext{10}& ext{11} & ext{12}& ext{13}& ext{14}& ext{15} & ext{16} & ext{17}\ hline S =& 1&a&1&b&1&b&1&a&1&h&1&o&1&p&1&o&1\ len =&1&2&1&2&5&2&1&2&1&1&1&2&1&4&1&2&1 \ end{array} ]

    则,其回文字符串的长度,就是(len_{i}) - 1


    重点就是对于(len_i)的求解,
    设置两个变量(mx)(id)(mx)表示以(id)为中心的最长的回文子串的右边界, 那么则有,(mx = id + len_{id}) ,

    如上图,假设我们现在进行求解(len_i),
    也就是以(i)为中心最长回文半径,如果(i<mx)那就是上图, (len_i=min(len_{id * 2 - i} ,mx-i)) ,其中(len_{id * 2 - i})就是(i)关于(id)的对称点 , 即上图的(len_{j})

    【复杂度】:

    关于这个算法的复杂度, 显然我不会证明, 但是线性的。
    结合下文的参考文献吧

    【参考文献】:

    1.https://segmentfault.com/a/1190000008484167
    2.https://www.zhihu.com/question/37289584(这个倒是多)

    【例题】:

    给出一个只由小写英文字符 ( exttt a, exttt b, exttt c,ldots exttt y, exttt z)组成的字符串 (S) ,求 (S) 中最长回文串的长度 。

    /*
     by : Zmonarch
     知识点 : Manacher
    */
    #include <bits/stdc++.h>
    using namespace std ;
    const int kmaxn = 1e8 ;
    int len[kmaxn] , ans;
    int mx , id ,cnt ;
    char s[kmaxn] , s1[kmaxn] ;//s1是旧的,s是新的
    void prepare()
    {
    	//将原来的字符串搞出来 
    	scanf("%s" , s1 +1) ;
    	int n = strlen(s1+1) ;
    	s[++cnt] = '~' ;
    	s[++cnt] = '#' ; //第一个数之前别忘了插入 
    	for(int i = 1 ; i <= n ; i++)
    	{
    		s[++cnt] = s1[i] ;
    		s[++cnt] = '#' ;
    	}
    	s[++cnt] = '^' ;
    }
    void manacher()
    {
    	for(int i = 2 ; i <= cnt - 1; i++)//过滤掉首尾的标示符 
    	{
    		if(i <= mx) len[i] = min(len[id * 2  - i ] , mx - i + 1) ;
    		else len[i] = 1 ; 
    		while(s[i - len[i]] == s[i + len[i]]) 
    		{
    			len[i]++;
    		}
    		if(i + len[i] > mx) 
    		{
    			mx = i + len[i] - 1 ;
    			id = i ;
    		}
    		//printf("%d
    ", ans) ; 
    		ans = max(ans , len[i]) ;
    	}
    	printf("%d", ans-1) ;
    }
    int main()
    {
    	prepare() ;
    	manacher() ;
    	return 0 ;
    } 
    
  • 相关阅读:
    mysql数据与Hadoop之间导入导出之Sqoop实例
    如何将mysql数据导入Hadoop之Sqoop安装
    大数据场景下数据异构之 Mysql实时写入HBase(借助canal kafka SparkStreaming)
    224 绝对路径与相对路径
    223 File类的常用方法
    222 File类概述
    221 输入输出流概述
    220 多线程总结
    219 线程间通信
    218 线程同步-synchronized
  • 原文地址:https://www.cnblogs.com/Zmonarch/p/14227129.html
Copyright © 2020-2023  润新知