一个字符串的答案,是把它的所有子串分组,满足每组中两两之间互为子串包含关系,最小的组数。
给一个字符串。若干个询问,每次询问一个区间的答案。
(n,qle 10^5)
暴力:建出后缀树,在这棵树上建后缀自动机。把转移边和fail边都丢进图中,然后跑最小链覆盖。跑一次时间是(O(n^3))。
最小链覆盖等于最长反链。
结论1:一定存在一条最长反链,使得反链上的字符串长度都相同。
证明:如果现在找到了一条最长反链,且字符串长度不全相同,则找到最小的(len),找长度为(len)的字符串(s[l,r]),满足(s[l-1,r-1])或(s[l+1,r+1])不在反链中。一定存在。(假设是(s[l+1,r+1])不在反链中)
(s[l,r+1])不在反链中,而它包含的子串是(s[l,r])的子串并(s[ldots r+1,r+1]),由于(len)最小,不存在(s[l+2dots r+1,r+1])的子串。于是可以把(s[l,r])替换成(s[l,r+1])。
一直这样做下去,最终可以使长度相同。
结论2:如果最长反链长度(ge k),当且仅当长度(n-k+1)的子串互不相同。
证明:充分性显然,接下来证必要性。
只考虑字符串长度相等的最长反链,设字符串长度为(len)。此时有(n-len+1ge k)。
反证,假设长度(n-k+1)的子串存在相同,设分别为(s[a,a+n-k],s[b,b+n-k])。则两个子串中长度为(len)的子串对应相同,即(s[a+i,a+i+len-1]=s[b+i,b+i+len-1],i+len-1le n-k)。于是对于(len)来说,本质不同的子串不超过(n-len+1-((n-k)-(len-1)+1)=k-1),矛盾。
推论:答案为:(n-max_{i<j} LCP(S[i:],S[j:]))。
相当于找到最大(k)满足(n-k+1)的子串互不相同。
也就是最小(len),子串互不相同,答案为(n-len+1)。此时(len-1),子串存在相同。
子串存在相同,即存在两个长度等于(len-1)的子串相同。
然后好做了。按照套路SAM+LCT搞,枚举右端点,然后access。LCT中记录最后一次的右端点。另外用个线段树,支持给([l,r])中每个位置(x),和(k-x)取(max)。修改时候,相当于找到上一次的endpos,它对应的区间([endpos-maxlen+1,endpos-minpos+1])每个位置(x)和(endpos+1-x)取(max)。时间(O(nlg^2 n))。
题解给出个不错的想法:在后缀树上对endpos集合启发式合并。合并之后,新形成的邻居(即合并(S_1,S_2),(xin S_1,yin S_2),(x,y)在(S_1igcup S_2)中相邻)可能对答案产生贡献,于是记下二元组((x,y))。根据启发式合并的性质知道这样的二元组有(O(nlg n))个。找出这些二元组之后扫描线,一样需要用到上面那棵线段树,时间相同。
using namespace std;
#include <bits/stdc++.h>
#define N 100005
#define fi first
#define se second
#define mp(x,y) make_pair(x,y)
#define ll long long
int n,Q;
char str[N];
int m,fa[N*2],id[N];
vector<pair<int,int> > q[N];
namespace SAM{
struct Node{
Node *c[26],*fail;
int len;
} d[N*2],*S,*T;
void insert(int ch){
Node *nw=&d[++m],*p;
nw->len=T->len+1;
for (p=T;p && !p->c[ch];p=p->fail)
p->c[ch]=nw;
if (!p)
nw->fail=S;
else{
Node *q=p->c[ch];
if (p->len+1==q->len)
nw->fail=q;
else{
Node *clone=&d[++m];
memcpy(clone,q,sizeof(Node));
clone->len=p->len+1;
for (;p && p->c[ch]==q;p=p->fail)
p->c[ch]=clone;
nw->fail=q->fail=clone;
}
}
T=nw;
}
void build(){
S=T=&d[++m];
for (int i=1;i<=n;++i)
insert(str[i]-'a'),id[i]=T-d;
for (int i=2;i<=m;++i)
fa[i]=d[i].fail-d;
}
}
struct Node{
Node *fa,*c[2];
int isr;
bool getson(){return fa->c[0]!=this;}
void rotate(){
Node *y=fa,*z=y->fa;
if (y->isr!=-1)
isr=y->isr,y->isr=-1;
else
z->c[y->getson()]=this;
int k=getson();
fa=z;
y->c[k]=c[k^1],c[k^1]->fa=y;
c[k^1]=y,y->fa=this;
}
void splay(){
for (;isr==-1;rotate())
if (!fa->isr)
getson()!=fa->getson()?rotate():fa->rotate();
}
} d[N*2],*null,*rt;
namespace SGT{
int tag[N*4],mx[N*4];
void modify(int st,int en,int c,int k=1,int l=1,int r=n){
if (c<=tag[k])
return;
if (st<=l && r<=en){
tag[k]=c;
mx[k]=max(max(mx[k<<1],mx[k<<1|1]),tag[k]-l);
return;
}
int mid=l+r>>1;
if (st<=mid) modify(st,en,c,k<<1,l,mid);
if (mid<en) modify(st,en,c,k<<1|1,mid+1,r);
mx[k]=max(max(mx[k<<1],mx[k<<1|1]),tag[k]-l);
}
int query(int st,int en,int k=1,int l=1,int r=n){
if (st<=l && r<=en)
return mx[k];
int mid=l+r>>1,res=tag[k]-max(l,st);
if (st<=mid) res=max(res,query(st,en,k<<1,l,mid));
if (mid<en) res=max(res,query(st,en,k<<1|1,mid+1,r));
return res;
}
}
void modify(int i){
Node *x=&d[id[i]],*y=null;
for (;x!=rt;y=x,x=x->fa){
x->splay();
if (x->isr){
int l=SAM::d[x->fa-d].len+1,r=SAM::d[x-d].len;
// printf("%d %d %d
",x->isr-r+1,x->isr-l+1,x->isr);
SGT::modify(x->isr-r+1,x->isr-l+1,x->isr+1);
}
x->c[1]->isr=x->isr;
x->c[1]=y;
y->isr=-1;
x->isr=i;
}
}
int ans[N];
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
scanf("%d%d",&n,&Q);
scanf("%s",str+1);
for (int i=1;i<=Q;++i){
int l,r;
scanf("%d%d",&l,&r);
q[r].push_back(mp(l,i));
}
SAM::build();
null=d;
*null={null,null,null,-1};
for (int i=1;i<=m;++i)
d[i]={&d[fa[i]],null,null,0};
rt=&d[1];
for (int i=1;i<=n;++i){
modify(i);
for (int j=0;j<q[i].size();++j){
int l=q[i][j].fi;
ans[q[i][j].se]=i-l+1-max(SGT::query(l,i),0);
}
}
for (int i=1;i<=Q;++i)
printf("%d
",ans[i]);
return 0;
}