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