1. 程式人生 > 其它 >【題解】P4070 [SDOI2016]生成魔咒(SAM/字尾自動機)

【題解】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;
}