bzoj3676/洛谷3649 [Apio2014]回文串

bzoj3676/洛谷3649 [Apio2014]回文串

题意:找到出现次数 长度最大的回文子串

方法一:通过manacher找到回文串,然后到SAM中搜有多少个…..
然而…会t….
而我们会发现在SAM中搜有多少个,也就是找到这个回文串的最后一个字符,它的size
所以倍增直接找到最后一个点就可以了

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std; 
#define N 300010
#define ll long long
char str[N];ll ans=0;
int root,last,cnt=0,n,len[N<<1],size[N<<1],fa[N<<1],son[N<<1][26],c[N<<1],a[N<<1],mx[N<<1],d[N<<1],f[N<<1][20],pos[N];
inline void ins(int ch,int id){
    int p=last,np=++cnt;last=np;len[np]=len[p]+1;size[np]++;pos[id]=cnt;
    while(p && !son[p][ch]) son[p][ch]=np,p=fa[p];
    if(!p) fa[np]=root;
    else{
        int q=son[p][ch];
        if(len[q]==len[p]+1) fa[np]=q;
        else{
            int nq=++cnt;len[nq]=len[p]+1;
            memcpy(son[nq],son[q],sizeof(son[q])); 
            fa[nq]=fa[q];fa[q]=fa[np]=nq;
            while(son[p][ch]==q) son[p][ch]=nq,p=fa[p];
        }
    }
}
inline void calc(){
    for(int i=1;i<=cnt;i++) c[len[i]]++;
    for(int i=1;i<=cnt;i++) c[i]+=c[i-1];
    for(int i=cnt;i>=1;i--) a[c[len[i]]--]=i;
    for(int i=cnt;i>=1;i--) size[fa[a[i]]]+=size[a[i]];
    for(int i=1;i<=cnt;i++){
        int q=a[i];d[q]=d[fa[q]]+1;f[q][0]=fa[q];
        for(int j=1;(1<<j)<=d[q];j++){
            if(!f[q][j-1]) break;
            else f[q][j]=f[f[q][j-1]][j-1];
        }
    }
}
inline void query(int x,int y){
    int p=pos[y];
    for(int i=19;i>=0;i--){
        if(len[f[p][i]]>=y-x+1) p=f[p][i];
    }ans=max(ans,(ll)size[p]*(y-x+1));
}
inline void manacher(){
    int maxright=0,mid=0;
    str[0]='+';str[n+1]='-';
    for(int i=1;i<=n;i++){
        if(i<maxright) mx[i]=min(mx[2*mid-i-1],maxright-i);
        else mx[i]=0;
        while(str[i+mx[i]+1]==str[i-mx[i]]) mx[i]++,query(i-mx[i]+1,i+mx[i]);
        if(maxright<i+mx[i]) mid=i,maxright=i+mx[i];
    }maxright=0;
    for(int i=1;i<=n;i++){
        if(i<maxright) mx[i]=min(mx[2*mid-i],maxright-i-1);
        else mx[i]=1,query(i-mx[i]+1,i+mx[i]-1);
        while(str[i+mx[i]]==str[i-mx[i]]) mx[i]++,query(i-mx[i]+1,i+mx[i]-1);
        if(maxright<i+mx[i]) mid=i,maxright=i+mx[i];
    }
}
int main(){
    scanf("%s",str+1);n=strlen(str+1);last=root=++cnt;
    for(int i=1;i<=n;i++) ins(str[i]-'a',i);
    calc();manacher();
    printf("%lld\n",ans);
    return 0;
} 

方法二:这个方法就比较大神了
来自于2015年张天扬集训队论文
先按照原串建立SAM,然后用反串在上面跑匹配,如果[lr]这一段能匹配上,并且覆盖了当前节点的最右边的一个数maxpos(这个在做完SAM以后要跟着size一起更新),那么说明 [l,pos] [ l , p o s ] 这一段一定是回文,因此找它的个数就可以了
还是很难理解啊…还是看代码吧…

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std; 
#define N 300010
#define ll long long
char str[N];ll ans=0;
int root,last,cnt=0,n,size[N<<1],fa[N<<1],son[N<<1][26],c[N<<1],a[N<<1],mx[N<<1],pos[N<<1],vis[N<<1];
inline void ins(int ch,int id){
    int p=last,np=++cnt;last=np;mx[np]=mx[p]+1;size[np]++;pos[np]=id;
    while(p && !son[p][ch]) son[p][ch]=np,p=fa[p];
    if(!p) fa[np]=root;
    else{
        int q=son[p][ch];
        if(mx[q]==mx[p]+1) fa[np]=q;
        else{
            int nq=++cnt;mx[nq]=mx[p]+1;pos[nq]=id;
            memcpy(son[nq],son[q],sizeof(son[q])); 
            fa[nq]=fa[q];fa[q]=fa[np]=nq;
            while(son[p][ch]==q) son[p][ch]=nq,p=fa[p];
        }
    }
}
int main(){
    scanf("%s",str+1);n=strlen(str+1);last=root=++cnt;
    for(int i=1;i<=n;i++) ins(str[i]-'a',i);
    for(int i=1;i<=cnt;i++) c[mx[i]]++;
    for(int i=1;i<=cnt;i++) c[i]+=c[i-1];
    for(int i=cnt;i>=1;i--) a[c[mx[i]]--]=i;
    for(int i=cnt;i>=1;i--) size[fa[a[i]]]+=size[a[i]],pos[fa[a[i]]]=max(pos[fa[a[i]]],pos[a[i]]);
    int p=root,l=0;
    for(int i=n;i>=1;i--){
        int ch=str[i]-'a';
        while(p && !son[p][ch]) p=fa[p],l=mx[p];
        if(son[p][ch]) p=son[p][ch],l++;
        if(i+l>pos[p]){
            if(i<pos[p]) ans=max(ans,(ll)size[p]*(pos[p]-i+1));
            for(int q=p;q && !vis[q];q=fa[q]){
                vis[q]=true;
                if(i<=pos[q] && pos[q]<=i+mx[q]-1) ans=max(ans,(ll)size[q]*(pos[q]-i+1)); 
            }
        }
    } printf("%lld\n",ans);
    return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值