dtoi2680「SDOI2016」生成魔咒

题意:

     魔咒串由许多魔咒字符组成,魔咒字符可以用数字表示。例如可以将魔咒字符 $ 1 $、$ 2 $ 拼凑起来形成一个魔咒串 $ [1, 2] $。

     一个魔咒串 $ S $ 的非空子串被称为魔咒串 $ S $ 的生成魔咒。

     例如 $ S = [1, 2, 1] $ 时,它的生成魔咒有 $ [1] $、$ [2] $、$ [1, 2] $、$ [2, 1] $、$ [1, 2, 1] $ 五种。$ S = [1, 1, 1] $ 时,它的生成魔咒有 $ [1] $、$ [1, 1] $、$ [1, 1, 1] $ 三种。

     最初 $ S $ 为空串。共进行 $ n $ 次操作,每次操作是在 $ S $ 的结尾加入一个魔咒字符。每次操作后都需要求出,当前的魔咒串 $ S $ 共有多少种生成魔咒。

     对于 $ 100\% $ 的数据,$ 1 \leq n \leq 100000 $。

     用来表示魔咒字符的数字 $ x $ 满足 $ 1 \leq x \leq 10 ^ 9 $。

题解:

     考虑使用后缀自动机,对于第 $i$ 个字符的加入,显然会产生一些区间 $[l,i]$ 作为答案的贡献,$l$的范围一定介于 $1$ 与某个数字之间。思考后缀自动机的本质,后缀自动机的每一个节点,表示相同的“结尾集合”的所有子串,对于每一个子串,他必然在字符串中出现至少 $1$ 次,而每一次出现的右端点的集合就称之为“结尾集合”(这似乎只有我这么叫)。那么在这题中,$[1,i]$ 一定只出现了 $1$ 次,那么,与他所在同一个节点的所有字符串,也是只出现了 $1$ 次,且恰好是新出现的。

     所以这题就很容易了,每次答案的增加量都是新建节点的 $maxlen$ 减去它父亲的 $maxlen$,不过这题值域较大,可以用 $map$ 维护。

#include<cstdio>
#include<map>
#include<algorithm>
#include<cstdlib>
using namespace std;
int n,cur=1,cnt=1,last,fa[200002],dis[200002];
long long ans;
map<int,int>ch[200002];
void build(int c,int id){
    last=cur;cur=++cnt;
    int p=last;dis[cur]=id;
    for (;p&&!ch[p].count(c);p=fa[p])ch[p][c]=cur;
    if (!p)fa[cur]=1; 
    else
    {
        int q=ch[p][c];
        if (dis[q]==dis[p]+1)fa[cur]=q;
        else
        {
            int nt=++cnt;dis[nt]=dis[p]+1;
            ch[nt]=ch[q];
            fa[nt]=fa[q];fa[q]=fa[cur]=nt;
            for (;ch[p].count(c)&&ch[p][c]==q;p=fa[p])ch[p][c]=nt;
        }
    }
}
int main()
{
    scanf("%d",&n);int nw=1;
    for (int i=1;i<=n;i++)
    {
        int a;scanf("%d",&a);
        build(a,i);nw=ch[nw][a];
        ans+=dis[nw]-dis[fa[nw]];
        printf("%lld\n",ans);
    }
    return 0;
}
posted @ 2021-02-17 21:19  1124828077ccj  阅读(76)  评论(0编辑  收藏  举报