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);
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章