bzoj4516: [Sdoi2016]生成魔咒

4516: [Sdoi2016]生成魔咒

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 575  Solved: 327
[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

Analysis

      题目是让你回答每个前缀i中出现了多少种本质不同的字符串。
      有一种算法是求解整个字符串中出现了多少不同的字符串,这个在罗穗骞的《后缀数组——处理字符串的有力工具》中出现过,就是从上往下扫描height数组,每次给答案累加sa[i]+1-height[i],也就是这个串和前面的串不同的前缀的数目。
      设想一:用上面说的方法扫描,令f[i]表示以第i个字符为结尾的本质不同的子串的数目,那么最后f数组的前i项和就是以i为结尾的本质不同的字符串的个数。统计方法:对于每个字符串,给f[sa[i]+height[i]]到f[n]加1,用树状数组优化到NlogN
      推翻设想一:上面的算法保证了每个本质不同的子串都仅被统计一次,所以最后一项的答案一定是正确的,但其它项的答案可能是不正确的,举个例子,假如后最排序后有这样的几个相邻项:
                 ab........
                 abc....(这个串位置比较靠后)
                 abcde.....(这个串位置比较靠前)
      那么abc这个串被加到了比较靠后的位置,而比较靠前的位置中就没有记录它的信息,因此abc虽然在比较靠前的位置出现了,但答案中没有记录,所以前面的答案就是错误的。
      问题来了,一个串如何只被统计在它最早出现的串中。
      设想二:改变扫描的顺序,从长度最长的后缀开始(也就是开头最靠前的后缀),把它所有的前缀都统计一下,加到对应的f数组里;然后再做次长的后缀,把它和最长后缀取一下最长公共前缀,那么除了最长公共前缀之外的前缀就都是新出现的子串,把那些子串加到f数组对应的位置上;............;第i长的后缀,要和比它长的所有后取一下最长公共前缀(取其中的最大值),新出现的前缀进行统计......以此反复。初步的时间复杂度,求SA+ST表+和前面所有的取最长公共前缀,即O(NlogN+N^2)
      设想二的正确性:每个子串在第一次出现时,都被统计到了对应的位置上,且由于我们减去了最长公共前缀,每个子串不会被重复统计,也就是说,我们成功地把每个串统计在了它的第一次出现位置上
      进一步优化:复杂度主要高在“与所有比它长的串取最长公共前缀”上。不难想出,最长公共前缀在后缀数组中是向上、向下都单调不上升的,因此我们并没有必要跟所有比它唱的串都取最长公共前缀,只需向上、向下分别找第一个长度比它长的,取一下这中间所有height的最小值就好了。我们维护一个单调栈,长度单调递减。具体怎么弄就不说了,不是重点,反正正着做一遍、再反着做一遍就行了,最终的时间复杂度O(NlogN+N),如果后缀数组用O(N)的预处理,复杂度将变成O(N),已经是最好的算法了
RunIDUserProblemResultMemoryTimeLanguageCode_LengthSubmit_Time
16459587103956414516Accepted12564 kb676 msC++/Edit2352 B2016-09-25 13:40:54

Code

//bzoj4516: [Sdoi2016]生成魔咒 后缀数组
#include <cstdio>
#include <algorithm>
#include <map>
#include <cmath>
#define maxn 100050
#define lowbit(x) (x&-x)
using namespace std;
int sa[maxn], rank[maxn], height[maxn], wa[maxn], wb[maxn], ws[maxn],
	wv[maxn], n, m, x[maxn], delt[maxn], tmp[maxn], st[maxn][18],
	stack[maxn], top, f[maxn];
long long ans;
map<int,int> hash;
bool cmp(int *r, int a, int b, int l)
{return r[a]==r[b] and r[a+l]==r[b+l];}
void build_SA(int *r, int *sa, int n, int m)
{
	int i, j, p, k=0, *x=wa, *y=wb, *t;
	for(i=0;i<m;i++)ws[i]=0;
	for(i=0;i<n;i++)ws[x[i]=r[i]]++;
	for(i=1;i<m;i++)ws[i]+=ws[i-1];
	for(i=n-1;i>=0;i--)sa[--ws[x[i]]]=i;
	for(p=1,j=1;p<n;j<<=1,m=p)
	{
		for(p=0,i=n-j;i<n;i++)y[p++]=i;
		for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j;
		for(i=0;i<n;i++)wv[i]=x[y[i]];
		for(i=0;i<m;i++)ws[i]=0;
		for(i=0;i<n;i++)ws[wv[i]]++;
		for(i=1;i<m;i++)ws[i]+=ws[i-1];
		for(i=n-1;i>=0;i--)sa[--ws[wv[i]]]=y[i];
		for(t=x,x=y,y=t,x[sa[0]]=0,p=1,i=1;i<n;i++)
			x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
	}
	for(i=1;i<n;i++)rank[sa[i]]=i;
	for(i=0;i<n-1;height[rank[i++]]=k)
		for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
}
int lisan()
{
	int i, j, tot=0;
	for(i=0;i<n;i++)
		if(hash.find(x[i])==hash.end())hash[x[i]]=++tot;
	for(i=0;i<n;i++)x[i]=hash[x[i]];
	return tot;
}
void work()
{
	int i, j, k, l, t;
	for(i=1;i<=n;i++)st[i][0]=height[i];
	for(k=1;k<=17;k++)
		for(i=1;i+(1<<k-1)<=n;i++)
			st[i][k]=min(st[i][k-1],st[i+(1<<k-1)][k-1]);
	for(i=1,top=0;i<=n;i++)
	{
		while(sa[stack[top]]>sa[i] and top)top--;
		t=stack[top]+1;
		l=(int)log2(i-t+1);
		f[i]=min(st[t][l],st[i-(1<<l)+1][l]);
		stack[++top]=i;
	}
	for(i=n,stack[top=0]=n+1;i;i--)
	{
		while(sa[stack[top]]>sa[i] and top)top--;
		t=stack[top];
		l=(int)log2(t-i);
		f[i]=max(f[i],min(st[i+1][l],st[t-(1<<l)+1][l]));
		stack[++top]=i;
	}
	for(i=1;i<=n;i++)delt[sa[i]+f[i]]++;
}
int main()
{
	int i;
	long long now=0, ans=0;
	scanf("%d",&n);
	for(i=0;i<n;i++)scanf("%d",x+i);
	build_SA(x,sa,n+1,lisan()+1);
	work();
	for(i=0;i<n;i++)now+=delt[i],ans+=now,printf("%lld\n",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值