[BZOJ 2865]字符串识别(后缀数组+线段树)
作者:互联网
[BZOJ 2865]字符串识别(后缀数组+线段树)
题面
给定一个字符串S,与一个整数K,定义S的子串T=S(i, j)是关于第K位的识别子串,满足以下两个条件:
1、i≤K≤j。
2、子串T只在S中出现过一次。
现在,给定S,XX希望知道对于S的每一位,最短的识别子串长度是多少.
\(|S| \leq 5 \times 10^5\)
分析
引理:在字符串\(S\)的某个后缀\(i\)的所有前缀中,最短的识别子串的长度为\(\max(height[rank[i]],height[rank[i]+1])+1\)
证明: 感性理解,与后缀i字典序最接近的后缀,LCP长度一定更大。那么把这两个height取max,就得到了最长的LCP,再+1一定不会有重复的子串(否则height会更大).所以这一定是最短的识别子串
令\(len=\max(height[rank[i]],height[rank[i]+1])+1\),
那么这个识别子串对于\([i,i+len-1]\)的这几位的答案,一定有len的贡献,于是把\([i,i+len-1]\)的答案和len取min
并且,对于\(\forall j \in [i+len,n]\),子串\(s[i,j]\)一定是识别子串。因为若\(s[i,j]\)重复出现,那\(s[i,i+len-1]\)也一定重复出现,与引理矛盾。那么\([i+len,n]\)上的位置\(j\)的答案与\(j-i+1\)取min
第一种情况很好解决,用标记永久化的线段树区间修改即可。查询的时候把叶子节点到根上的所有标记取min即为答案。
第二种情况同理,只是我们只维护\(-i+1\)的最小值,查询第\(j\)个位置的时候先同情况一在线段树上查询,然后把结果加上\(j\)即可。
这样用两棵线段树维护两种情况的答案即可。时间复杂度\(O(n \log n)\)
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define INF 0x3f3f3f3f
#define maxn 500000
#define maxs 128
using namespace std;
inline void qprint(int x){
if(x<0){
putchar('-');
qprint(-x);
}else if(x==0){
putchar('0');
return;
}else{
if(x>=10) qprint(x/10);
putchar('0'+x%10);
}
}
void rsort(int *ans,int *fi,int *se,int n,int m){
static int buck[maxn+5];
for(int i=0;i<=m;i++) buck[i]=0;
for(int i=1;i<=n;i++) buck[fi[i]]++;
for(int i=1;i<=m;i++) buck[i]+=buck[i-1];
for(int i=n;i>=1;i--) ans[buck[fi[se[i]]]--]=se[i];
}
int sa[maxn+5],rk[maxn+5],height[maxn+5];
void suffix_sort(char *s,int n,int m){
static int se[maxn+5];
for(int i=1;i<=n;i++){
rk[i]=s[i];
se[i]=i;
}
rsort(sa,rk,se,n,m);
for(int k=1;k<=n;k*=2){
int p=0;
for(int i=n-k+1;i<=n;i++) se[++p]=i;
for(int i=1;i<=n;i++) if(sa[i]>k) se[++p]=sa[i]-k;
rsort(sa,rk,se,n,m);
swap(rk,se);
rk[sa[1]]=1;
p=1;
for(int i=2;i<=n;i++){
if(se[sa[i-1]]==se[sa[i]]&&se[sa[i-1]+k]==se[sa[i]+k]) rk[sa[i]]=p;
else rk[sa[i]]=++p;
}
if(p==n) break;
m=p;
}
}
void get_height(char *s,int n,int m){
suffix_sort(s,n,m);
for(int i=1;i<=n;i++) rk[sa[i]]=i;
int k=0;
for(int i=1;i<=n;i++){
if(k) k--;
int j=sa[rk[i]-1];
while(s[i+k]==s[j+k]) k++;
height[rk[i]]=k;
}
}
struct segment_tree{
struct node{
int l;
int r;
int mark;//永久化标记
}tree[maxn*4+5];
void build(int l,int r,int pos){
tree[pos].l=l;
tree[pos].r=r;
tree[pos].mark=INF;
if(l==r) return;
int mid=(l+r)>>1;
build(l,mid,pos<<1);
build(mid+1,r,pos<<1|1);
}
void update(int L,int R,int val,int pos){
if(L<=tree[pos].l&&R>=tree[pos].r){
tree[pos].mark=min(tree[pos].mark,val);
return;
}
//标记永久化,不用push_down
int mid=(tree[pos].l+tree[pos].r)>>1;
if(L<=mid) update(L,R,val,pos<<1);
if(R>mid) update(L,R,val,pos<<1|1);
}
int query(int qpos,int pos){
if(tree[pos].l==tree[pos].r){
return tree[pos].mark;
}
int mid=(tree[pos].l+tree[pos].r)>>1;
if(qpos<=mid) return min(tree[pos].mark,query(qpos,pos<<1));
else return min(tree[pos].mark,query(qpos,pos<<1|1));
}
}T1,T2;
int n;
char s[maxn+5];
int main(){
#ifdef LOCAL
// freopen("1.in","r",stdin);
// freopen("1.ans","w",stdout);
#endif
scanf("%s",s+1);
n=strlen(s+1);
get_height(s,n,maxs);
T1.build(1,n,1);
T2.build(1,n,1);
for(int i=1;i<=n;i++){
int len=max(height[rk[i]],height[rk[i]+1])+1;
if(i+len-1<=n) T1.update(i,i+len-1,len,1);
if(i+len-1<n) T2.update(i+len,n,-i+1,1);//第j位的答案是j-i+1,不存储j,只存储min(-i+1)
}
for(int i=1;i<=n;i++){
qprint(min(T1.query(i,1),T2.query(i,1)+i));
putchar('\n');
}
}
标签:子串,2865,后缀,pos,len,height,int,se,BZOJ 来源: https://www.cnblogs.com/birchtree/p/12246503.html