[Luogu P4070] [BZOJ 4516] [SDOI2016]生成魔咒

本文介绍了一种算法,用于动态维护一个由魔咒字符组成的字符串的所有不同子串的数量,通过使用SAMSAM(Suffix Automaton)数据结构,每次在字符串末尾添加一个字符时,都能快速更新生成魔咒的数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

洛谷传送门
BZOJ传送门

题目描述

魔咒串由许多魔咒字符组成,魔咒字符可以用数字表示。例如可以将魔咒字符 1 , 2 1,2 1,2 拼凑起来形成一个魔咒串 [ 1 , 2 ] [1,2] [1,2]

一个魔咒串 S S S 的非空字串被称为魔咒串 S S S 的生成魔咒。

例如 S = [ 1 , 2 , 1 ] S=[1,2,1] S=[1,2,1] 时,它的生成魔咒有 [ 1 ] [1] [1] [ 2 ] [2] [2] [ 1 , 2 ] [1,2] [1,2] [ 2 , 1 ] [2,1] [2,1] [ 1 , 2 , 1 ] [1,2,1] [1,2,1] 五种。 S = [ 1 , 1 , 1 ] S=[1,1,1] S=[1,1,1] 时,它的生成魔咒有 [ 1 ] [1] [1] [ 1 , 1 ] [1,1] [1,1] [ 1 , 1 , 1 ] [1,1,1] [1,1,1] 三种。最初 S S S 为空串。共进行 n n n 次操作,每次操作是在 S S S 的结尾加入一个魔咒字符。每次操作后都需要求出,当前的魔咒串 S S S 共有多少种生成魔咒。

输入输出格式

输入格式:

第一行一个整数 n n n

第二行 n n n 个数,第 i i i 个数表示第 i i i 次操作加入的魔咒字符。

输出格式:

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

输入输出样例

输入样例#1:
7
1 2 3 3 3 1 2
输出样例#1:
1
3
6
9
12
17
22

说明

对于10%的数据, 1 ≤ n ≤ 10 1 \le n \le 10 1n10

对于30%的数据, 1 ≤ n ≤ 100 1 \le n \le 100 1n100

对于60%的数据, 1 ≤ n ≤ 1000 1 \le n \le 1000 1n1000

对于100%的数据, 1 ≤ n ≤ 100000 1 \le n \le 100000 1n100000

用来表示魔咒字符的数字 x x x 满足 1 ≤ n ≤ 1 0 9 1 \le n \le 10^9 1n109

解题分析

动态维护本质不同的子串数, 实际上就是 S A M SAM SAM上每个点的 l e n len len减去其 p a r e n t parent parent节点的 l e n len len的和。 我们在修改 p a r e n t parent parent的时候顺带维护即可。

代码如下:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <cmath>
#include <map>
#define R register
#define IN inline
#define W while
#define MX 505000
#define ll long long
int par[MX], len[MX];
std::map <int, int> to[MX];
int last, cur, l, cnt;
ll ans;
namespace SAM
{
	IN int get(R int now) {return (~par[now]) ? len[now] - len[par[now]] : 0;}
	IN void insert(R int id)
	{
		R int now = last, tar;
		cur = ++cnt; len[cur] = len[last] + 1; last = cur;
		for (; (~now) && !to[now][id]; now = par[now]) to[now][id] = cur;
		if(now < 0) return par[cur] = 0, ans += get(cur), void();
		tar = to[now][id];
		if(len[tar] == len[now] + 1) return par[cur] = tar, ans += get(cur), void();
		int nw = ++cnt; len[nw] = len[now] + 1; 
		par[nw] = par[tar]; ans += get(nw) - get(tar);
		par[tar] = par[cur] = nw; ans += get(cur) + get(tar);
		to[nw] = to[tar];
		for (; (~now) && to[now][id] == tar; now = par[now]) to[now][id] = nw;
	}
}
int main(void)
{
	par[0] = -1;
	int T, buf; scanf("%d", &T);
	W (T--)
	{
		scanf("%d", &buf);
		SAM::insert(buf); printf("%lld\n", ans);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值