ACM-ICPC 2018 南京赛区网络预赛 I.Skr (后缀数组+Manacher)

题目大意:求出一个字符串的所有回文子串

网上大多数题解都是回文自动机的解法,上次在群里问了dalao们,说可以用后缀数组+Manache搞一搞,终于有时间了,于是就补了这道题~~~

首先我们用Manacher算法求出该字符串每一个位置作为回文中点最大的回文长度

然后用后缀数组求出其每一个后缀的height值

对于每一个位置,我们枚举这个位置能够形成了哪些和前面后缀能形成的不同的回文子串

枚举每个回文子串的时候,分为两种情况:

1.回文子串长度为奇数,该位置恰好为字符串中点

2.回文子串长度为偶数,该位置为对称位置靠右边的位置

如果不去掉重复的回文子串的话,那么就能直接通过上面的方法直接枚举所有回文子串

但是因为我们要去掉重复的回文子串,所以我们要减去这个字符串和前面串重复的最大的长度,也就是说找到前面每一个字符串和其最大的重复位置,也就是min(lcp,前面子串形成的回文字符串的最大长度)

对于奇数和偶数情况,我们分别维护一个和前面重复的最大位置,然后统计的时候直接忽略这些就行了

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long llt;
//找到一个字符串内不同的回文子串有多少个
//后缀数组+Manacher
const int MOD=1e9+7;
const int maxn=2100000;
int str[maxn];
int wa[maxn],wb[maxn],wv[maxn],wss[maxn];
int sa[maxn],ranks[maxn],height[maxn];
bool cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(int *r,int n,int m)
{
    int i,j,k=0,p,*x=wa,*y=wb,*t;
    for(i=0;i<m;i++) wss[i]=0;
    for(i=0;i<n;i++) wss[x[i]=r[i]]++;
    for(i=1;i<m;i++) wss[i]+=wss[i-1];
    for(i=n-1;i>=0;i--) sa[--wss[x[i]]]=i;
    for(j=1,p=1;p<n;j<<=1,m=p)
    {
        for(p=0,i=n-j;i<n;i++) y[p++]=i;
        for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
        for(i=0;i<n;i++) wv[i]=x[y[i]];
        for(i=0;i<m;i++) wss[i]=0;
        for(i=0;i<n;i++) wss[wv[i]]++;
        for(i=1;i<m;i++) wss[i]+=wss[i-1];
        for(i=n-1;i>=0;i--) sa[--wss[wv[i]]]=y[i];
        for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    }
    for(i=0;i<n;i++) ranks[sa[i]]=i;
    for(i=0;i<n-1;i++)
    {
        if(k) k--;
        j=sa[ranks[i]-1];
        while(r[i+k]==r[j+k]) k++;
        height[ranks[i]]=k;
    }
    return;
}
int Ma[maxn<<1],Mp[maxn<<1];
void Manache(int s[],int len)
{
    int l=0;
    Ma[l++]='$';
    Ma[l++]='#';
    for(int i=0;i<len;i++)
    {
        Ma[l++]=s[i];
        Ma[l++]='#';
    }
    Ma[l]=0;
    int mx=0,id=0;
    for(int i=0;i<l;i++)
    {
        Mp[i]=mx>i?min(Mp[2*id-i],mx-i):1;
        while(Ma[i+Mp[i]]==Ma[i-Mp[i]]) Mp[i]++;
        if(i+Mp[i]>mx)
        {
            mx=i+Mp[i];
            id=i;
        }
    }
}
//就是后缀数组在扫一遍的时候记录每一个回文子串
//不过要注意的就是回文子串的长度为奇数和偶数的情况要拿出啦讨论
llt sum[maxn],pww[maxn];
char s[maxn];
llt getsum(int l,int r)
{
    if(!l) return sum[r];
    else return (sum[r]-pww[r-l+1]*sum[l-1]%MOD+MOD)%MOD;
}
int main()
{
    while(scanf("%s",s)!=EOF)
    {
        int len=strlen(s);
        for(int i=0;i<len;i++) str[i]=s[i];
        str[len]=0;
        Manache(str,len);
        da(str,len+1,233);
        sum[0]=str[0]-'0';
        pww[0]=1;
        for(int i=1;i<len;i++) {
            sum[i]=(sum[i-1]*10+(str[i]-'0'))%MOD;
            pww[i]=(pww[i-1]*10)%MOD;
        }
        llt ans=0;
        int tmp=0,tmp2=0;
        for(int i=1;i<=len;i++)
        {
            //枚举长度为奇数的不相同的回文子串
            int rep=(Mp[sa[i]*2+2])/2;
            int rep2=(Mp[sa[i]*2+1]-1)/2;
            //printf("At[%d] :%d %d\n",i,rep,rep2);
            //枚举长度为奇数的不相同的回文串
            if(height[i]<=tmp) tmp=height[i];
            else tmp=max(tmp,min(height[i],(Mp[sa[i-1]*2+2])/2));
            for(int j=tmp+1;j<=rep;j++)
                ans=(ans+getsum(sa[i]-j+1,sa[i]+j-1))%MOD;
            //然后枚举长度为偶数的不相同的回文子串
            if(height[i]<=tmp2) tmp2=height[i];
            else tmp2=max(tmp2,min(height[i],(Mp[sa[i-1]*2+1]-1)/2));
            for(int j=tmp2+1;j<=rep2;j++)
                ans=(ans+getsum(sa[i]-j,sa[i]+j-1))%MOD;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值