BZOJ4516 [Sdoi2016]生成魔咒 后缀自动机

BZOJ4516 [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 共有多少种生成魔咒。

输入格式

第一行一个整数 n。

第二行 n 个数,第 i 个数表示第 i 次操作加入的魔咒字符。

1≤n≤100000。用来表示魔咒字符的数字 x 满足 1≤x≤10^9

输出格式

输出 n 行,每行一个数。第 i 行的数表示第 i 次操作后 S 的生成魔咒数量

样例输入

7
1 2 3 3 3 1 2

样例输出

1
3
6
9
12
17
22


x可能有很多取值,用SAM搞的话需要用map记录状态转移。

如果只是求一个状态的答案,那么可以在原图上跑拓扑排序+DP,时间复杂度 O(n) 级别。然而如果每一个状态都这样求一次,那么时间复杂度就到了 O(n2) ,这下怎么办?

考虑每次加一个字符后对状态转移的改变是困难的。需要找到另一种方法。这需要理解SAM的原理才能做。

SAM是根据增量法构造的,每次增加的目的就是要把新的字符串的后缀添加到SAM的状态中,同时做到不重不漏。假设上一次的答案是Ans,那么这一次的答案就是 Ans+cur_len-重复的字符串个数 。重复的字符串怎么知道呢?其数值就是Max[par[np]]。


#include<stdio.h>
#include<map>
#include<algorithm>
#define MAXN 400005
#define ll long long
using namespace std;

int N;

long long Ans;

int par[MAXN],Max[MAXN],tot,las,rt;
map<int,int>Son[MAXN];

int push(int val)
{
    Max[++tot]=val;
    return tot;
}

void Ins(int t)
{
    int p,q,np,nq;
    np=push(Max[las]+1);
    for(p=las;p&&(!Son[p][t]);p=par[p])Son[p][t]=np;
    if(!p)par[np]=rt;
    else
    {
        q=Son[p][t];
        if(Max[q]==Max[p]+1)par[np]=q,Ans-=Max[q];
        else
        {
            nq=push(Max[p]+1);
            Son[nq]=Son[q];
            par[nq]=par[q];
            par[q]=par[np]=nq;
            Ans-=Max[nq];
            for(;Son[p][t]==q;p=par[p])Son[p][t]=nq;
        }
    }
    las=np;
}

int main()
{
    int i,x;

    scanf("%d",&N);
    rt=las=push(0);
    for(i=1;i<=N;i++)
    {
        scanf("%d",&x);
        Ans=Ans+i;
        Ins(x);
        printf("%lld\n",Ans);
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值