【题解】P4070 [SDOI2016]生成魔咒(SAM/后缀自动机)

原题链接

[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\) 次操作加入的魔咒字符 \(x_i\)

输出格式

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

样例 #1

样例输入 #1

7
1 2 3 3 3 1 2

样例输出 #1

1
3
6
9
12
17
22

提示

数据规模与约定

对于 \(10\%\) 的数据,保证 \(1 \le n \le 10\)
对于 \(30\%\) 的数据,保证 \(1 \le n \le 100\)
对于 \(60\%\) 的数据,保证 \(1 \le n \le 10^3\)
对于 \(100\%\) 的数据,保证 \(1 \le n \le 10^5\)\(1 \leq x_i \leq 10^9\)

题解

考虑SAM中的link指向的是和它endpos不同的最长后缀,新加点的长度减去它的link的长度即增加的长度,直接建SAM即可。

#include<bits/stdc++.h>
using namespace std;
inline int rd(){
	int f=1,j=0;
	char w=getchar();
	while(!isdigit(w)){
		if(w=='-')f=-1;
		w=getchar();
	}
	while(isdigit(w)){
		j=j*10+w-'0';
		w=getchar();
	}
	return f*j;
}

const int N=100001;
struct node{
	unordered_map<int,int>to;
	int len,fro;
}sam[N*2];
int n,last,cnt;
long long ans;

void insert(int c){
	int p=last,now=++cnt;
	sam[now].len=sam[p].len+1;
	while(p&&(!sam[p].to[c]))sam[p].to[c]=now,p=sam[p].fro;
	last=now;
	if(!p)return sam[now].fro=1,void(0);
	if(sam[p].len+1==sam[sam[p].to[c]].len)return sam[now].fro=sam[p].to[c],void(0);
	int clone=++cnt,qnode=sam[p].to[c];
	sam[clone]=sam[qnode];
	sam[clone].len=sam[p].len+1;
	sam[qnode].fro=sam[now].fro=clone;
	while(p&&sam[p].to[c]==qnode)sam[p].to[c]=clone,p=sam[p].fro;
	return ;
}

signed main(){
	n=rd();
	last=cnt=1;
	for(int i=1;i<=n;i++){
		int x=rd();
		insert(x);
		ans+=sam[last].len-sam[sam[last].fro].len;
		printf("%lld\n",ans);
	}
	return 0;
}
posted @ 2022-12-12 16:59  flywatre  阅读(48)  评论(0)    收藏  举报