(manacher)马拉车算法专题题目

manacher算法用来求解回文串问题,时间复杂度为O(n).

不懂的先可以去练习下模板板子题,求最长回文串

传送门P3501 [POI2010]ANT-Antisymmetry
这一题他给的是一个新的定义“反对称”字符串:
如果将这个字符串0和1取反后,再将整个串反过来和原串一样,就称作“反对称”字符串。比如00001111和010101就是反对称的,1001就不是。
我们可以了利用0/1的性质,由于0/1始终不能与自己相反,故子串并不存在中央位置,我们只能从自己添加的特殊符号’#'的奇数位上找
,差不多就是manacher的一个模板题,就是循环时的判定条件发生了变化,回文串求的是str[i - Len[i]]== str[i + Len[i]],这里我们可以直接check来判定

bool check(char i, char j) {
	if(((i - '0') ^ (j - '0')) == 1) return 1;//如果俩个数相反
	if(i == '#' && j == '#') return 1;//如果特殊符号相等返回1
	return 0;
}

不知名的代码

#include<bits/stdc++.h>
using namespace std;
const int N=2000010;
int p[N],n,m;
char s[N],str[N];
int len;

bool check(char i, char j) {
	if(((i - '0') ^ (j - '0')) == 1) return 1;//如果俩个数相反
	if(i == '#' && j == '#') return 1;//如果特殊符号相等返回1
	return 0;
}
void manacher()
{
    p[0]=0;
    int mx=0;
    int id=0;
    int k=0;
    str[k++]='%';
    str[k++]='#';
    for(int i=0;i<n;i++)
    {
        str[k++]=s[i];
        str[k++]='#';
    }
    len=k;
    for(int i=1;i<len;i+=2)//数字只会出现在偶数上,我们只要枚举所有的奇数位'#'来作为分割线
    {
        if(i<mx) p[i]=min(mx-i,p[2*id-i]);
        else p[i]=1;
        while(check(str[i-p[i]],str[i+p[i]])) p[i]++;//改变判定条件
        if(p[i]+i>mx)
        {
            mx=p[i]+i;
            id=i;
        }
    }long long  ans=0;
    // cout<<len<<endl;
    // for(int i=1;i<=len-1;i++)
    // cout<<str[i]<<" ";
    // cout<<endl;
    for(int i=1;i<=len-1;i+=2)
    {
        // cout<<p[i]<<"   ";
        if(p[i]>1) ans+=p[i]/2;
    }
        //  if(p[i]>1) ans+=p[i]/2;
     cout<<ans<<endl;
    
}

int main()
{
    scanf("%d",&n);
    scanf("%s",s);
    manacher();
    return 0;
}

不知道为什么p[i]/2的可以自己枚举一下

P4555 [国家集训队]最长双回文串

这一题又是一个整活题目,他要求的是最长双回文串
想到回文串我们就能利用manacher算法
该题目的双回文串就是:把一个字符串baacaabbacabb拆开
可以拆开为aacaa,bbacabb两个回文子串,求这个两个回文子串的最大长度是多少

我们可以利用’#'来当作切割的位置,求这个位置作为回文串的左端点的最大长度和作为右端点的最大长度,然后枚举一边加起来即可。
中间转移时我们可以利用dp

  for(int i=1 ;i<=len-1 ;i+=2) 
    {
        L[i] = max(L[i] , L[i-2] -2 );
    }

这里转移是我们找最大长度,L[i-2]是我们只找’#‘上的位置,
L[i-2]-2是该位置L[i]在上一个位置的后面两位,回文串的长度也要 -2
转移过程

不知名代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+10;
int Len[N],r[N];
int len;
int R[N],L[N];
char s[N],str[N];
int n,m;

void manacher()
{
    // memset(str,0,sizeof(str));
    len=strlen(s);
    int k=0;
    str[k++] = '$';
    for(int i=0;i<len;i++){
        str[k++]='#';
        str[k++]=s[i];
    }
    str[k++]='#';
    len=k;
    Len[0]=0;
    int mx=0;
    int id=0;
      for(int i=1 ;i<len ;i++)
    {
        if( i < mx ) Len[i] = min(Len[2*id-i] , mx-i);
        else Len[i]=1;
        while( str[i-Len[i]] == str[i+Len[i]] ) Len[i]++;
        if( Len[i] + i  > mx)
        {
            mx = Len[i] + i;
            id = i;
        }
        int r = i+Len[i]-1;
        int l = i-Len[i]+1;
       // printf("%d %d %d %d\n",i,r,l,Len[i]-1);
        L[l] = max(Len[i]-1,L[l]);//以l为回文串的左端点的最长回文串
        R[r] = max(Len[i]-1,R[r]);//以r为回文串的右端点的最长回文串
    }
}

int main()
{
    scanf("%s",s);
    manacher();
    
    int ans=0;
      for(int i=1 ;i<=len-1 ;i+=2) 
    {
        L[i] = max(L[i] , L[i-2] -2 );
    }
    //for(int i=1;i<=len;i+=2)
    //cout<<L[i]<<" ";
    
   for(int i=len-1 ;i>=1 ;i-=2) R[i] = max(R[i] , R[i+2] - 2 );
    for(int i=1 ;i<=len-1;i+=2)
    {if(R[i]&&L[i])
        ans = max(ans , L[i] + R[i]);
    }
      cout<<ans<<endl;
     
    return 0;
}

P1659 [国家集训队]拉拉队排练

这题求的是前K大的回文串长度的乘积,也相当于一个板子题。
这题有个巨恶心的地方,最后一个点数据很大,要用快速幂qmi
来写,然后这题只要我们求回文串长度为奇数的回文串,最后枚举所有回文串长度时偶数直接可以跳过

不知名的代码

#include<bits/stdc++.h>
using namespace std;
const int N=3000010,mod=19930726;
typedef long long ll;
int Len[N];
char s[N],str[N];
ll cnt[N],sum,id,len;
ll n,m,K;

void manacher()
{
    int k=0;
    str[k++]='%';
    for(int i=0;i<n;i++)
    {
        str[k++]='#';
        str[k++]=s[i];
    }
    str[k++]='#';
    len=k;
    // cout<<len<<endl;
    Len[0]=0;
    int mx=0;
    for(int i=1;i<len;i++)
    {
        if(i<mx) Len[i]=min(mx-i,Len[2*id-i]);
        else Len[i]=1;
        while(str[Len[i]+i]==str[i-Len[i]]) Len[i]++;
        if(Len[i]+i>mx)
        {
            mx=Len[i]+i;
            id=i;
        }
        if((Len[i]-1)%2) cnt[Len[i]-1]++;
    }
    // cout<<sum-1<<endl;
}


ll qmi(int x,int y)
{
    if(x==1) return 1;
    ll base=x,res=1;
    while(y)
    {
        if(y&1) res=(res*base)%mod;
        base=(base*base)%mod;
        y>>=1;
    }
    return res;
}


void solve()
{
    ll ans=1;
    sum=0;
    for(int i=n;i>=1;i--)
    {
       
        if(i%2==0) continue; 
        // printf("%d %d\n",i,cnt[i]);
        sum+=cnt[i];
        if(K>=sum)
        {
            ans*=qmi(i,sum)%mod;
            K-=sum;
        }
        else 
        {
            ans*=qmi(i,K)%mod;
            K-=sum;break;
        }
       // cout<<ans<<endl;
    }
    if(K>0) cout<<-1<<endl;
    else printf("%lld\n",ans);
    
}

int main()
{
    scanf("%d%lld",&n,&K);
    scanf("%s",s);
    manacher();
    solve();
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
算法是一种常用于字符串匹配的算法,其核心思想是利用回文串的对称性来减少比较次数。Java中可以通过以下步骤实现算法: 1. 预处理字符串,将字符串中的每个字符用一个特殊字符隔开,如:将字符串"abc"变成"#a#b#c#" 2. 维护一个数组P,其中P[i]表示以i为中心的最长回文子串的半径长度。具体实现时,可以使用一个中心点center和右边界right来维护,其中center表示当前已知的最长回文子串的中心点,right表示该回文子串的右边界。根据回文串的对称性,可以利用已知回文串的左侧字符的对称点来推出右侧字符的回文半径。 3. 遍历字符串,根据P数组更新center和right,并记录最长回文子串的起始位置和长度。 以下是Java代码实现示例: ```java public class ManacherAlgorithm { public static String longestPalindrome(String s) { if (s == null || s.length() == 0) { return ""; } StringBuilder sb = new StringBuilder(); sb.append("#"); for (int i = 0; i < s.length(); i++) { sb.append(s.charAt(i)); sb.append("#"); } String str = sb.toString(); int[] P = new int[str.length()]; int center = 0, right = 0; int start = 0, maxLen = 0; for (int i = 0; i < str.length(); i++) { if (i < right) { P[i] = Math.min(right - i, P[2 * center - i]); } while (i - P[i] - 1 >= 0 && i + P[i] + 1 < str.length() && str.charAt(i - P[i] - 1) == str.charAt(i + P[i] + 1)) { P[i]++; } if (i + P[i] > right) { center = i; right = i + P[i]; } if (P[i] > maxLen) { start = (i - P[i]) / 2; maxLen = P[i]; } } return s.substring(start, start + maxLen); } public static void main(String[] args) { String s = "babad"; System.out.println(longestPalindrome(s)); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值