HDU2019多校第二场 1009(HDU 6599) I Love Palindrome String(回文树(自动机)+manacher)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6599

解题思路:

回文自动机求每个本质不同的子串出现的次数,同时记录每个节点i代表的回文串第一次出现的起点终点。

然后对原串跑一遍马拉车

然后遍历一遍每个节点代表的回文串(最多 有 字符串长度 个本质不同的回文)

枚举每个起点终点,用马拉车判断前一半是否回文,累加答案即可。

回文树的代码来自:https://blog.csdn.net/u013368721/article/details/42100363

代码:

#include<bits/stdc++.h>

using namespace std;

const int maxn = 3e5+5;
const int ALP = 26;

char s[maxn];
char tmp[maxn<<1];
int Len[maxn<<1],ans[maxn];

int INIT(char *st)
{
    int i,len=strlen(st);
    tmp[0]='@';
    for(i=1;i<=2*len;i+=2)
    {
        tmp[i]='#';
        tmp[i+1]=st[i/2];
    }
    tmp[2*len+1]='#';
    tmp[2*len+2]='$';
    tmp[2*len+3]=0;
    return 2*len+1;
}

int MANACHER(char *st,int len)
{
     int mx=0,po=0;
     for(int i=1;i<=len;i++)
     {
         if(mx>i)
         Len[i]=min(mx-i,Len[2*po-i]);
         else
         Len[i]=1;
         while(st[i-Len[i]]==st[i+Len[i]])
         Len[i]++;
         if(Len[i]+i>mx)
         {
             mx=Len[i]+i;
             po=i;
         }
     }
}

struct PAM{ // 每个节点代表一个回文串
    int next[maxn][ALP]; // next指针,参照Trie树
    int fail[maxn]; // fail失配后缀链接
    int cnt[maxn]; // 此回文串出现个数
    int num[maxn];
    int len[maxn]; // 回文串长度
    int s[maxn]; // 存放添加的字符
    int last; //指向上一个字符所在的节点,方便下一次add
    int n; // 已添加字符个数
    int p; // 节点个数
    int l[maxn],r[maxn];//i节点代表的回文串第一次出现最左左右字符是第几个(不是下标)

    int newnode(int w){///新建一个节点,长度初始化为这个节点代表的回文串长度
        for(int i=0;i<ALP;i++)
            next[p][i] = 0;
        cnt[p] = 0;
        num[p] = 0;
        len[p] = w;
        return p++;
    }
    void init(){
        p = 0;
        newnode(0);
        newnode(-1);
        last = 0;
        n = 0;
        s[n] = -1; // 开头放一个字符集中没有的字符,减少特判
        fail[0] = 1;
    }
    int get_fail(int x){ // 和KMP一样,失配后找一个尽量最长的
        while(s[n-len[x]-1] != s[n]) x = fail[x];
        return x;
    }
    void add(int c){
        c -= 'a';
        s[++n] = c;
        int cur = get_fail(last);
        if(!next[cur][c]){
            int now = newnode(len[cur]+2);///len赋值在这
            fail[now] = next[get_fail(fail[cur])][c];
            next[cur][c] = now;
            num[now] = num[fail[now]] + 1;
            l[now] = n-len[now]+1;
            r[now] = n;
        }
        last = next[cur][c];
        cnt[last]++;
    }
    void count(){
        // 最后统计一遍每个节点出现个数
        // 父亲累加儿子的cnt,类似SAM中parent树
        // 满足parent拓扑关系
        for(int i=p-1;i>=0;i--)
            cnt[fail[i]] += cnt[i];
    }
}pam;

int main()
{
    while (~scanf("%s",s)){
        memset(ans,0,sizeof ans);
        int len = strlen(s);
        int llen = INIT(s);
        MANACHER(tmp,llen);
        pam.init();
        for (int i=0;i<len;i++) pam.add(s[i]);
        pam.count();
        for (int i = 2;i<pam.p;i++){
            int l = pam.l[i],r = pam.r[i];
            if (Len[l+(l+r>>1)]-1>=(l+r>>1)-l+1) ans[r-l+1] += pam.cnt[i];
        }

        for (int i=1;i<=len;i++){
            if (i!=1) printf(" ");
            printf("%d",ans[i]);
        }puts("");
    }
    return 0;
}


总结:

wdnmd真就这么裸的题。

首先尝试了一下manacher暴力做,超时超的怀疑人生。

然后大改总结出了这么点东西:

马拉车+hash不适合求所有本质不同的回文串的的数量,因为马拉车本身就是利用已经是回文串的部分,通过跳过这一部分以减低复杂度的,而这里跳过的部分恰巧是我们需要统计的。你可以用已匹配的部分暴力抠出来回文,也可以马拉车求出当前最长回文之后对最长的扣回文,不过复杂度应该都退化成O(N^2)

当然马拉车+hash还是可以求本质不同回文子串总的种类数,因为已匹配部分肯定被记录过,接下去要匹配的部分可能出现过,可能没出现过,这就需要哈希去重。

也可以求回文串总数,按照奇偶分类我们可以得到每个节点为中心的最长的回文串,自然就可以得到所有以该节点的为中心的。

GET 回文树功能1,求所有本质不同的子串的数量,以及该字符串具体是哪一个(起点,终点):时间复杂度 O(N*字符种类)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值