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