4516: [Sdoi2016]生成魔咒
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 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
1 2 3 3 3 1 2
Sample Output
1
3
6
9
12
17
22
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),已经是最好的算法了
RunID | User | Problem | Result | Memory | Time | Language | Code_Length | Submit_Time |
1645958 | 710395641 | 4516 | Accepted | 12564 kb | 676 ms | C++/Edit | 2352 B | 2016-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;
}