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,時間複雜度
考慮每次加一個字符後對狀態轉移的改變是困難的。需要找到另一種方法。這需要理解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);
}
}