zoukankan      html  css  js  c++  java
  • manacher马拉车算法(学习笔记)

    博主是在大佬博客这里学的.

    例题:给出一个只由小写英文字符a,b...y,z组成的字符串S,求S中最长回文串的长度.字符串长度为n;

    由于回文分为偶回文(比如 bccb)和奇回文(比如 bcacb),而在处理奇偶问题上会比较繁琐,所以这里我们使用一个技巧,具体做法是:在字符串首尾,及各字符间各插入一个字符(前提这个字符未出现在串里)。例如:

    bccb ----> ~|b|c|c|b|

    定义一个数组int f[],其中f[i]表示以 i 为中心的最长回文的半径;例如字符串bcacb(这个地方不能理解的,自己在纸上举几个例子就明白了):

    f[1]=1, f[2]=1, f[3]=3, f[4]=1, f[5]=1;

    设置两个变量maxr 和 mid 。maxr 代表以 mid 为中心的最长回文的右边界,也就是maxr = mid + f[mid]。

    注意:maxr不是我们读入的字符串的右边界,而是我们当前已知的回文串的右边界,我们就是要不断使它往右扩展,即回文串长度不断增大

    假设我们现在求f[i],也就是求以 i 为中心的最长回文半径,如果i < maxr,那么:

    
    if(i<maxr){
    
      f[i]=min(f[(mid<<1)-i],maxr-i);
    
    }
    
    

    2 * mid - i是 i 关于 mid 的对称点(可根据中点公式证明),设i,j关于mid对称,则(i+j)/2=mid,j=2*mid-i;

    f[j]表示以 j 为中心的最长回文半径,因此我们可以利用f[j]来加快查找。

    
    #include<bits/stdc++.h>
    using namespace std;
    int cnt,maxr,mid,f[25000005];
    
    //cnt表示读入字符个数,maxr,mid,f数组意义如上文所述,
    int maxans;                           
    //记录答案,即最长回文串的长度
    char ch,s[25000005];          
    //ch循环读入字符所用,s数组储存读入的字符串
    int main(){
         s[++cnt]='~';            
    //使字符串第一个字符为'~',具体作用请见下面代码
         s[++cnt]='|';      
         while(~scanf("%c",&ch)){
              if(ch>='a'&&ch<='z'){
                 s[++cnt]=ch;
                 s[++cnt]='|';         
      //读入字符串的同时,读入分隔符
           }
           else break;
         }
         for(int i=1;i<=cnt;i++){   
      //枚举读入的每个字符
           if(i<maxr) 
                 f[i]=min(f[(mid<<1)-i],maxr-i); 
     //maxr表示以 mid 为中心的最长回文串的右边界;
    //(mid<<1)-i表示i关于mid的对称点;
    //maxr-i表示半径可达到的最大值(i+f[i]<=maxr);
           else f[i]=1;                                              
     //否则该字符本身构成一个独立的回文串,半径为1
           while(s[i-f[i]]==s[i+f[i]]) 
           	  f[i]++;                 
    //以i为中心,i-f[i],i+f[i]分别是回文串左右两边端点
    //如果相等,说明以i为中心的回文串的半径还可以继续增大
    //此处体现了我们之前使字符串0号下标为字符'~'的作用
    //防止数组越界,使循环暂停;
           if(f[i]+i>maxr) maxr=f[i]+i,mid=i;           
    //如果可以更新右边界maxr,则更新右边界;
    //同时也要更新mid(mid随maxr变化);
           if(f[i]>maxans) maxans=f[i];                 
    //更新最大答案
         }
         printf("%d",maxans-1);                                
    //输出ans-1,为什么要减1,自己手动举几个例子就明白了
         return 0;
    }
    
    

    练习:对于一个长度为偶数的序列(a_1,a_2,a_3...a_n),定义这个序列为好的序列,当且仅当(a_1+a_n=a_2+a_{n-1}=a_3+a_{n-2}...);

    定义一个对序列的翻滚操作,使所有元素向前移一个位置,第一个元素移到最后的位置;现在给你一个长度为偶数的序列(b_1,b_2,b_3...b_n),请问至少需要翻滚多少次才能使这个序列成为好的序列;

    #include<bits/stdc++.h>
    #define ll long long
    #define inf 0x3f3f3f3f
    #define db double
    using namespace std;
    int l[2000005],a[4000005];
    int n,i,mid,r,t;
    ll tot;
    inline int read(){
        char ch;
        while((ch=getchar())<'0'||ch>'9');
        	int res=ch^48;
        while((ch=getchar())>='0'&&ch<='9')
        	res=(res<<1)+(res<<3)+(ch^48);
        return res;
    }
    int main(){
        n=read();
        for(i=1;i<=n;++i){
            a[i+n]=a[i]=read();
            tot+=a[i];
        }
        //数组倍长,计算总和tot
        t=tot/(n/2);
        //计算每对匹配元素的和
        i=0;
        a[0]=-100000000;
        //a[0]赋极大值,作用于下面循环停止
        while(i<=n){//马拉车模板
        	if(i<r)
                l[i]=min(l[(mid<<1)-i],r-i);
            while(a[i+l[i]]+a[i-l[i]+1]==t)
                l[i]++;
            if(l[i])
                l[i]--;
            if(l[i]*2>=n){
                printf("%d
    ",i-(n/2));
                return 0;
            }
            if(i+l[i]>r){
                r=i+l[i];
                mid=i;
            }
        	i++;
        }
        printf("IMPOSSIBLE
    ");
        return 0;
    }
    
    
  • 相关阅读:
    C#枚举扩展方法,获取枚举值的描述值以及获取一个枚举类下面所有的元素
    C#对IQueryable<T>、IEnumerable<T>的扩展方法
    C#常用处理数据类型转换、数据源转换、数制转换、编码转换相关的扩展
    C#常用8种排序算法实现以及原理简介
    身份证号合法性验证,支持15位和18位身份证号,支持地址编码、出生日期、校验位验证
    C#检验参数合法性公用方法
    MVC中得到成员元数据的Description特性描述信息公用方法
    把对象类型转换成指定的类型帮助类方法
    C#操作图片帮助类
    C#执行Dos命令公用方法
  • 原文地址:https://www.cnblogs.com/PPXppx/p/9861089.html
Copyright © 2011-2022 走看看