HDU3948:后缀数组+马拉车(本质不同回文子串统计)

题意:给出一个字符串,求其 本质不同的 回文子串的个数。


如果有小伙伴WA了无数次,请尝试模拟一下aabaa这个串,答案应该是5。(本菜鸡就WA了一晚上)


题解:回文子串可以考虑先来个O(n)的马拉车预处理,这样每个回文子串长度必然是计数,那么我们可以统计本质不同的(正中间的字符+右半边串)回文子串个数。然后可以考虑用后缀自动机统计答案。这道题的关键的关键在于去重的处理。去重要求去掉:h[i]范围内已经被统计过的串。那么可以用一个变量维护 目前已经被统计过的长度。要注意到h数组和马拉车的lc数组是没什么关系的。


Code:

#include<bits/stdc++.h>
using namespace std;
#define rank rk
const int MAX = 2e5+10000;
char ch[MAX];
int cntA[MAX],cntB[MAX],A[MAX],B[MAX],tsa[MAX],rank[MAX],SA[MAX],lc[MAX],h[MAX];
int n,t;
int Cas =1; 
void init(){
    memset(ch,0,sizeof ch);
    ch[0]='z'+1;
}
void input(){
    scanf("%s",ch+1);
    n =  strlen(ch+1);
    ch[n*2+1]='#';
    for (int i=n;i>=1;i--){
        ch[i*2] = ch[i];
        ch[i*2-1] ='#';
    }
    n = n*2+1;
    ch[n+1]='\0';
}
void get_SA(){
    for (int i=0;i<=10000;i++) cntA[i]=0;
    for (int i=1;i<=n;i++) cntA[ch[i]]++;
    for (int i=1;i<=10000;i++) cntA[i]+=cntA[i-1];
    for (int i=n;i>=1;i--) SA[cntA[ch[i]]--] =i;
    rank[SA[1]]=1;
    for (int i=2;i<=n;i++){
        rank[SA[i]]=rank[SA[i-1]];
        if (ch[SA[i]]!=ch[SA[i-1]]) rank[SA[i]]++;
    }
    for (int step = 1;rank[SA[n]]<n;step<<=1){
        for (int i=0;i<=n;i++)cntA[i]=cntB[i]=0;
        for (int i=1;i<=n;i++){
            cntA[A[i]=rank[i]]++;
            cntB[B[i]=(i+step<=n)?rank[i+step]:0]++;
        }
        for (int i=1;i<=n;i++) cntA[i]+=cntA[i-1],cntB[i]+=cntB[i-1];
        for (int i=n;i>=1;i--) tsa[cntB[B[i]]--] =i;
        for (int i=n;i>=1;i--) SA[cntA[A[tsa[i]]]--] = tsa[i];
        rank[SA[1]]=1;
        for (int i=2;i<=n;i++){
            rank[SA[i]]=rank[SA[i-1]];
            if (A[SA[i]]!=A[SA[i-1]]||B[SA[i]]!=B[SA[i-1]]) rank[SA[i]]++;
        }
    }
}
void get_Height(){
    for (int i=1,j=0;i<=n;i++){
        if (j) j--;
        while (ch[i+j]==ch[SA[rank[i]-1]+j])j++;
        h[rank[i]]=j;
    }
}
void Manacher(){
    lc[1]=1;
    int k=1;
    for (int i=2;i<=n;i++){
//        printf("%d %d\n",i,k);
        int p = k+lc[k]-1;
        if (i<=p){
            lc[i]=min(lc[2*k-i],p-i+1);
        }else{
            lc[i]=1;
        }
        while (ch[i+lc[i]]==ch[i-lc[i]])lc[i]++;
        if (i+lc[i]>k+lc[k])k=i;
    }
}
void print(){
    printf("%s\n",ch+1);
    for (int i=1;i<=n;i++){
        printf("%s %d\n",ch+SA[i],lc[SA[i]]);
    }
}
void solve(){
    get_SA();
    get_Height();
    Manacher();
    print();
    long long res =0;
//    cout<<"1: "<<res<<endl;
	int cnt=0;
    for (int i=2;i<=n;i++){
    	cnt = min(cnt,h[i]);
        res+=max(0,lc[SA[i]]-min(h[i],cnt));
  //      cout<<i<<" "<<res<<endl;
        if (lc[SA[i]]>cnt){
        	cnt = lc[SA[i]];
        }
    }
//    cout<<res/2<<endl;
    printf("Case #%d: %I64d\n",Cas++,res/2);
}
int main(){
    scanf("%d",&t);
    while (t--){
        init();
        input();
        solve();
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值