4516: [Sdoi2016]生成魔咒

4516: [Sdoi2016]生成魔咒

Time Limit: 10 Sec   Memory Limit: 128 MB
Submit: 642   Solved: 353
[ Submit][ Status][ Discuss]

Description

魔咒串由许多魔咒字符组成,魔咒字符可以用数字表示。例如可以将魔咒字符 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 共有多少种生成魔咒。

Input

第一行一个整数 n。
第二行 n 个数,第 i 个数表示第 i 次操作加入的魔咒字符。
1≤n≤100000。,用来表示魔咒字符的数字 x 满足 1≤x≤10^9

Output

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

Sample Input

7
1 2 3 3 3 1 2

Sample Output

1
3
6
9
12
17
22

HINT

Source

[ Submit][ Status][ Discuss]



显然需要一个能识别所有子串的数据结构,后缀自动机是个不错的选择

而且其构造过程也是动态的,刚好满足题中的询问

根据后缀自动机的定义,每个点维护的len与其父亲len的差值就是该点维护的子串个数

把这些差值求和就是每次的答案了

实际上每次只是新增一个点的差值(另一个可能新增的抵消了)故很好维护

但是此题字符集偏大,O(10^5)

不过边显然没这么多,每个点的边集用线段树维护即可

因为后缀自动机的点数和边数都是线性的,(见CLJ论文)

所以复杂度O(nlogn)

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;

typedef long long LL;
const int maxn = 1E5 + 10;
const int T = 60;

int n,cnt = 1,cur = 1,Cnt,rt,last,k,a[maxn],s[maxn],len[maxn*2],par[maxn*2]
	,root[maxn*2],lc[maxn*T],rc[maxn*T],siz[maxn*T],Name[maxn*T];	
LL Ans;

int Get_Num(int o,int l,int r,int pos)
{
	if (!siz[o]) return 0;
	if (l == r) return Name[o];
	int mid = (l + r) >> 1;
	if (pos <= mid) return Get_Num(lc[o],l,mid,pos);
	else return Get_Num(rc[o],mid+1,r,pos);
}

void Insert(int o,int l,int r,int pos,int np)
{
	if (l == r) {siz[o] = 1; Name[o] = np; return;}
	int mid = (l + r) >> 1;
	if (pos <= mid)
	{
		lc[o] = lc[o]?lc[o]:++Cnt;
		Insert(lc[o],l,mid,pos,np);
	}
	else
	{
		rc[o] = rc[o]?rc[o]:++Cnt;
		Insert(rc[o],mid+1,r,pos,np);
	}
	siz[o] = siz[lc[o]] + siz[rc[o]];
}

void Copy(int o1,int o2)
{
	siz[o2] = siz[o1]; Name[o2] = Name[o1];
	if (lc[o1])	lc[o2] = ++Cnt,Copy(lc[o1],lc[o2]);
	if (rc[o1]) rc[o2] = ++Cnt,Copy(rc[o1],rc[o2]);
}

void Extend(int x)
{
	int p = last,np = ++cnt; last = np; 
	len[np] = k; root[np] = ++Cnt;
	while (p && !Get_Num(root[p],1,cur,x))
		Insert(root[p],1,cur,x,np),p = par[p];
	if (!p) {par[np] = rt; Ans += 1LL*k; return;}
	
	int q = Get_Num(root[p],1,cur,x);
	if (len[q] - len[p] == 1)
		Ans += 1LL*(k - len[q]),par[np] = q;
	else
	{
		int nq = ++cnt; len[nq] = len[p] + 1;
		root[nq] = ++Cnt; Copy(root[q],root[nq]);
		par[nq] = par[q]; par[q] = par[np] = nq; 
		Ans += 1LL*(k - len[nq]);
		while (p && Get_Num(root[p],1,cur,x) == q)
			Insert(root[p],1,cur,x,nq),p = par[p];
	}
}

int main()
{
	#ifdef DMC
		freopen("DMC.txt","r",stdin);
	#endif
	
	cin >> n; rt = last = 1; root[rt] = ++Cnt;
	for (int i = 1; i <= n; i++) scanf("%d",&s[i]),a[i] = s[i];
	sort(a + 1,a + n + 1);
	for (int i = 2; i <= n; i++)
		if (a[i] != a[i-1]) a[++cur] = a[i];
	for (k = 1; k <= n; k++)
	{
		s[k] = lower_bound(a + 1,a + cur + 1,s[k]) - a;
		Extend(s[k]); printf("%lld\n",Ans);
	}
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值