HDU 5421 Victor and String(回文树前段插入)

本文介绍了如何在回文树中进行前缀插入,并详细解析了HDU 5421 Victor and String问题。关键改动包括维护两个回文串指针和在fail匹配过程中的不同处理。通过特定判断和优化,可以实现O(1)的时间复杂度输出。提供了操作说明及代码实现。

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

这题就是学一下回文树前段插入。

下面这个是论文原话。

因此对于我们最初的板子,我们有:

改动1:原本维护上一个节点所在的回文串的节点的指针---->两个指针(重点)

由于回文串最长回文前缀和后缀是一样的,所以实际上我们只需要在左右插入的时候维护两个不同的指针,左边插入的指针维护他右边那个字符所在的最长回文串所在节点,右边插入的指针维护他上一个字符所在的最长回文串所在节点。

论文中所说的需要特判的地方是什么呢?

就是插入某个字符时,如果当前节点的回文串长度=加入的字符总数,那么将另一边的指针改成当前回文串节点即可。

这样两边的指针永远指向最长的回文串。

改动2:沿着fail匹配的过程左右不相同

左边的匹配右边,右边的匹配左边,这个的话小问题,即代码中两个get_fail的区别。


下面是针对本题的:

操作1:直接前段插入即可

操作2:直接后端插入即可

操作3:节点总数-2即可

操作4:维护num数组表示当前i节点代表的回文串的回文后缀数目(原本的回文树里也有),一个变量cnt维护当前回文串总数,每插入一个字符,cnt就加上当前节点所在回文串的num,这样也能做到O(1)输出


代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
#include<map>
#include<set>

using namespace std;

#define ll long long
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
#define rof1(i,a,b) for (int i=a;i>=b;i--)
#define rof0(i,a,b) for (int i=a;i>b;i--)
#define pb push_back
#define fi first
#define se second
#define debug(x) printf("----Line %s----\n",#x)
#define pt(x,y) printf("%s = %d\n",#x,y)
#define INF 0x3f3f3f3f
#define df(x) ll x;scanf("%I64d",&x)
#define df2(x,y) ll x,y;scanf("%I64d %I64d",&x,&y)
#define mod 1000000007
#define duozu(T) int T;scanf("%d",&T);while (T--)

const int N = 1e5+5;

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

struct PAM{ // 每个节点代表一个回文串
    int next[maxn][ALP]; // next指针,参照Trie树
    int fail[maxn]; // fail失配后缀链接
    int num[maxn];
    int len[maxn]; // 回文串长度
    int s[maxn<<1]; // 存放添加的字符
    int l_last,r_last; //指向上一个字符所在的节点,方便下一次add
    int l,r; //r-l+1表示现在添加的字符数量
    int p; // 节点个数
    ll cnt;//记录当前回文串总数

    int newnode(int w){//新建一个节点,长度初始化为这个节点代表的回文串长度
        for(int i=0;i<ALP;i++)
            next[p][i] = 0;
        num[p] = 0;
        len[p] = w;
        return p++;
    }
    void init(){
        cnt = 0;
        p = 0;
        newnode(0);
        newnode(-1);
        l_last = r_last = 0;
        memset(s,-1,sizeof s);//这样就不用特判边界了
        l = maxn;
        r = maxn-1;///pam中的s数组开了两倍,l,r都在中间
        fail[0] = 1;
    }
    int get_fail_l(int x){
        while (s[l+len[x]+1] != s[l]) x = fail[x];
        return x;
    }
    int get_fail_r(int x){
        while (s[r-len[x]-1] != s[r]) x = fail[x];
        return x;
    }
    void ladd(int c){
        c -= 'a';
        s[--l] = c;
        int cur = get_fail_l(l_last);
        if (!next[cur][c]){
            int now = newnode(len[cur]+2);
            fail[now] = next[get_fail_l(fail[cur])][c];///直接父节点开始匹配就变成了自己,所以从父节点的最长回文后缀开始匹配
            next[cur][c] = now;                        ///从这里也能开出如果父节点最长回文后缀不存在,那么当前最长回文后缀就是最后一个字母
            num[now] = num[fail[now]] + 1;
        }
        l_last = next[cur][c];
        cnt += num[l_last];
        if (len[l_last]==r-l+1) r_last = l_last;
    }
    void radd(int c){
        c -= 'a';
        s[++r] = c;
        int cur = get_fail_r(r_last);
        if (!next[cur][c]){
            int now = newnode(len[cur]+2);
            fail[now] = next[get_fail_r(fail[cur])][c];
            next[cur][c] = now;
            num[now] = num[fail[now]] + 1;
        }
        r_last = next[cur][c];
        cnt += num[r_last];
        if (len[r_last]==r-l+1) l_last = r_last;
    }
}pam;

int main()
{
    int q;
    while (~scanf("%d",&q)){
        pam.init();
        while (q--){
            int op;
            char s[2];
            scanf("%d",&op);
            if (op==1) scanf("%s",s),pam.ladd(s[0]);
            if (op==2) scanf("%s",s),pam.radd(s[0]);
            if (op==3) printf("%d\n",pam.p-2);
            if (op==4) printf("%lld\n",pam.cnt);
        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值