peiwenjun's blog 没有知识的荒原

UOJ523 【美团杯2020】半前缀计数 题解

题目描述

给定长为 \(n\) 的字符串 \(s\) ,求可以被表示成 \(s[1\sim i]+s[j\sim k]\) 的本质不同字符串个数,其中 \(0\le i\lt j\le k\le n\)

数据范围

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

时间限制 \(\texttt{2s}\) ,空间限制 \(\texttt{512MB}\)

分析

假设字符串 \(t\) 符合要求,枚举 \(lcp(s,t)=i\) ,那么要求剩余部分是 \(s[i+1\sim n]\) 的子串并且首字母不是 \(s_{i+1}\)

对反串建 \(\text{SAM}\) ,倒序扫描 \(i\)

本质不同子串个数可以增量式维护,具体方法参见P4070 [SDOI2016] 生成魔咒

再容斥掉首字母为 \(s_{i+1}\) 的情况,由于我们是对反串建 \(\text{SAM}\) ,所以需要统计最后一个字符为 \(s_{i+1}\) 的本质不同子串个数。

注意到同一个 \(endpos\) 集合中的串最后一个字符相同,对每个字符 \(c\) 维护以其结尾的本质不同子串个数,插入时动态更新即可。

最后别忘了空串,时间复杂度 \(\mathcal O(n)\)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e6+5;
int n,lst=1,tot=1;
ll cur,res;
ll cnt[26];
char s[maxn];
struct node
{
    int ch[26];
    int fa,len;
}f[maxn];
void insert(int c)
{
    int p=lst,np=lst=++tot;
    f[np].len=f[p].len+1;
    while(p&&!f[p].ch[c]) f[p].ch[c]=np,p=f[p].fa;
    if(!p) f[np].fa=1;
    else
    {
        int q=f[p].ch[c];
        if(f[q].len==f[p].len+1) f[np].fa=q;
        else
        {
            int nq=++tot;
            f[nq]=f[q],f[nq].len=f[p].len+1;
            f[q].fa=f[np].fa=nq;
            while(p&&f[p].ch[c]==q) f[p].ch[c]=nq,p=f[p].fa;
        }
    }
    cur+=f[np].len-f[f[np].fa].len;
    cnt[c]+=f[np].len-f[f[np].fa].len;
}
int main()
{
    scanf("%s",s+1),n=strlen(s+1);
    for(int i=n;i>=1;i--)
    {
        insert(s[i]-'a');
        res+=cur-cnt[s[i]-'a']+1;
    }
    printf("%lld\n",++res);
    return 0;
}

posted on 2022-08-21 21:05  peiwenjun  阅读(10)  评论(0)    收藏  举报

导航