peiwenjun's blog 没有知识的荒原

CF1738H Palindrome Addicts 题解

题目描述

维护一个由小写字符构成的队列,初始为空。

\(q\) 次操作,每次会删除开头字符或在末尾加入一个新的字符,你需要求出每次操作后队列中元素构成的字符串的本质回文子串数量。

数据范围

  • \(1\le q\le 10^6\)

时间限制 \(\texttt{2s}\) ,空间限制 \(\text{1GB}\)

分析

显然可以上区间本质不同回文子串,不过两只 \(\log\) 需要卡卡常。

考虑维护前缀的 \(\text{PAM}\) ,记录 \(cov_p\) 表示节点 \(p\) 最后一次在 \(s\) 中出现的右端点,答案为 \(\sum_p[cov_p-len_p+1\ge l]\)

那么加入字符对 \(cov\) 的更新为链覆盖,采用懒标记的思想,只需要给最深的点打覆盖标记。

再来考虑删除,所有满足 \(cov_p-len_p+1=l\) 的节点都会被删除。

由于被删除的节点一定是叶子,删除时将覆盖标记传给父节点即可。

代码实现时需要记录每个点的儿子数以判断是否为叶子,否则有可能因为标记没有及时更新导致误删。

时间复杂度 \(\mathcal O(q)\)

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
int q,lst=1,tot=1;
int ch[maxn][26],len[maxn],fail[maxn];
int cnt[maxn],cov[maxn];
char op[5],s[maxn];
vector<int> vec[maxn];
int get(int p,int i)
{
    while(s[i-1-len[p]]!=s[i]) p=fail[p];
    return p;
}
void insert(int i,int c)
{
    int p=get(lst,i);
    if(!ch[p][c])
    {
        fail[++tot]=ch[get(fail[p],i)][c],ch[p][c]=tot;
        len[tot]=len[p]+2;
    }
    lst=ch[p][c];
}
int main()
{
    scanf("%d",&q),len[1]=-1,fail[0]=1;
    for(int l=1,r=0,res=0;q--;)
    {
        auto add=[&](int p,int t)
        {
            if(p<=1||t<=cov[p]) return ;
            if(!cov[p]) cnt[fail[p]]++,res++;
            cov[p]=t,vec[cov[p]-len[p]+1].push_back(p);
        };
        auto del=[&](int p)
        {
            if(cnt[p]||cov[p]-len[p]+1!=l) return ;
            cnt[fail[p]]--,res--,add(fail[p],cov[p]),cov[p]=0;
        };
        scanf("%s",op+1);
        if(op[2]=='u')
        {
            getchar(),s[++r]=getchar(),insert(r,s[r]-'a');
            while(r-len[lst]+1<l) lst=fail[lst];
            add(lst,r);
        }
        else
        {
            for(auto p:vec[l]) del(p);
            l++;
        }
        printf("%d\n",res);
    }
    return 0;
}

posted on 2023-06-12 13:56  peiwenjun  阅读(10)  评论(0)    收藏  举报

导航