后缀数组+平衡树--luoguP4070 [SDOI2016]生成魔咒

传送门

首先有一个结论:
一个串中本质不同的子串个数为 n × ( n + 1 ) 2 − ∑ i = 1 n h e i g h t [ i ] \frac{n\times(n+1)}{2}-\sum_{i=1}^nheight[i] 2n×(n+1)i=1nheight[i]

因为正着考虑的话每次要加很多个后缀,不好维护,所以可以把序列倒过来看,每次加一个字符相当于加一个后缀,那就可以先预处理出整个序列的 h e i g h t height height数组,每次加入一个 i i i会影响排在它前面的和后面的后缀,会减掉它们 h e i g h t height height m i n min min,再加上两个 h e i g h t height height,就相当于给 s u m h e i g h t sum_{height} sumheight加了一个 m a x max max,那这个前面后面的就可以用一个平衡树(其实就是 s e t set set)来维护,然后每次加上 m a x max max,再用长度减一下就是答案了

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#define maxn 100005
#define LL long long
using namespace std;

inline int rd(){
	int x=0,f=1;char c=getchar();
	while(c<'0' || c>'9') f=c=='-'?1:1,c=getchar();
	while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
	return x*f;
}

int n,m,a[maxn],s[maxn],sa[maxn],rk[maxn],tax[maxn],tp[maxn],h[maxn],st[maxn][20];
LL ans;
set<int> S;
set<int>::iterator it;

inline void rsort(){
	for(int i=1;i<=m;i++) tax[i]=0;
	for(int i=1;i<=n;i++) tax[rk[i]]++;
	for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
	for(int i=n;i;i--) sa[tax[rk[tp[i]]]--]=tp[i];
}

inline void ssort(){
	for(int i=1;i<=n;i++) rk[i]=s[i],tp[i]=i;
	rsort();
	for(int w=1,p=0;p<n && w<=n;w<<=1,m=p){
		p=0;
		for(int i=n-w+1;i<=n;i++) tp[++p]=i;
		for(int i=1;i<=n;i++)
			if(sa[i]>w) tp[++p]=sa[i]-w;
		rsort(); swap(rk,tp);
		rk[sa[1]]=p=1;
		for(int i=2;i<=n;i++)
			if(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+w]==tp[sa[i-1]+w])
				rk[sa[i]]=p;
			else rk[sa[i]]=++p;
	}
}

inline void get_h(){
	int j,k=0;
	for(int i=1;i<=n;i++){
		if(k) --k;
		j=sa[rk[i]-1];
		while(s[j+k]==s[i+k]) ++k;
		h[rk[i]]=k;
	}
}

inline void prework(){
	for(int i=1;i<=n;i++) st[i][0]=h[i];
	for(int j=1;(1<<j)<n;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
			st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}

inline int query(int l,int r){
	if(l>r) swap(l,r); ++l;
	int k=log2(r-l+1); return min(st[l][k],st[r-(1<<k)+1][k]);
}

int main(){
	n=rd();
	for(int i=1;i<=n;i++) a[i]=s[n-i+1]=rd();
	sort(a+1,a+n+1); m=unique(a+1,a+n+1)-a-1;
	for(int i=1;i<=n;i++) s[i]=lower_bound(a+1,a+m+1,s[i])-a;
	ssort(); get_h(); prework();
	for(int i=n;i;i--){
		it=S.lower_bound(rk[i]); int tmp=0;
		if(it!=S.end()) tmp=query(rk[i],*it);
		if(it!=S.begin()) it--,tmp=max(tmp,query(*it,rk[i]));
		ans+=tmp; printf("%lld\n",1LL*(n-i+1)*(n-i+2)/2-ans);
		S.insert(rk[i]);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值