[BZOJ 2865]字符串识别(后缀数组+线段树)(或后缀自动机+线段树)
题面
给定一个字符串S,与一个整数K,定义S的子串T=S(i, j)是关于第K位的识别子串,满足以下两个条件:
1、i≤K≤j。
2、子串T只在S中出现过一次。
现在,给定S,XX希望知道对于S的每一位,最短的识别子串长度是多少.
(|S| leq 5 imes 10^5)
分析
SA做法
引理:在字符串(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))
SAM做法
对于后缀自动机上的一个节点(x),它对应的子串中最短的一个的长度应该是(len(link(x))+1).记right集合中的最大值是(maxpos(x)).
记(r=maxpos(x),l=maxpos(x)-(len(link(x))+1)+1).考虑我们求本质不同的子串个数的方法,(x)中对应的(len(link(x))+1)这个子串一定在其他位置没有出现过,那么区间([l,r])的最短识别子串长度就可能是(link(x)+1=r-l+1).我们把区间([l,r])的答案和(link(x)+1)取min
显然一个识别子串加上一些字符后仍然也是识别子串,那么对于(p in [1,l-1]),它的最短识别子串长度可能是(r-p+1),同样取min即可。
线段树维护类似上述方法。
代码
#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('
');
}
}
SAM:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define INF 0x3f3f3f3f
#define maxn 500000
#define maxc 26
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);
}
}
struct SAM{
#define link(x) (t[x].link)
#define len(x) (t[x].len)
struct node{
int ch[maxc];
int link;
int len;
int sz;//right集合大小
int maxpos;//right集合中的最大值
}t[maxn*2+5];
const int root=1;
int ptr=1;
int last=root;
void extend(char ch,int pos){
int c=ch-'a';
int p=last,cur=++ptr;
len(cur)=len(p)+1;
t[cur].sz=1;
t[cur].maxpos=pos;
while(p&&t[p].ch[c]==0){
t[p].ch[c]=cur;
p=link(p);
}
if(p==0) link(cur)=root;
else{
int q=t[p].ch[c];
if(len(p)+1==len(q)) link(cur)=q;
else{
int clo=++ptr;
t[clo]=t[q];
len(clo)=len(p)+1;
t[clo].sz=0;
t[clo].maxpos=0;
link(q)=link(cur)=clo;
while(p&&t[p].ch[c]==q){
t[p].ch[c]=clo;
p=link(p);
}
}
}
last=cur;
}
void topo_sort(){
static int in[maxn+5];
queue<int>q;
for(int i=1;i<=ptr;i++) in[link(i)]++;
for(int i=1;i<=ptr;i++) if(!in[i]) q.push(i);
while(!q.empty()){
int x=q.front();
q.pop();
t[link(x)].sz+=t[x].sz;
t[link(x)].maxpos=max(t[link(x)].maxpos,t[x].maxpos);
in[link(x)]--;
if(!in[link(x)]) q.push(link(x));
}
}
void solve(char *s){
int len=strlen(s+1);
for(int i=1;i<=len;i++) extend(s[i],i);
topo_sort();
}
#undef len
#undef link
}S;
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;
}
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(){
scanf("%s",s+1);
n=strlen(s+1);
S.solve(s);
T1.build(1,n,1);
T2.build(1,n,1);
for(int i=1;i<=S.ptr;i++){
if(S.t[i].sz==1){
int r=S.t[i].maxpos,l=r-(S.t[S.t[i].link].len+1)+1;
T1.update(l,r,r-l+1,1);
T2.update(1,l-1,r+1,1);
}
}
for(int i=1;i<=n;i++){
qprint(min(T1.query(i,1),-i+T2.query(i,1)));
putchar('
');
}
}