UOJ608. 【UR #20】机器蚤分组
作者:互联网
一个字符串的答案,是把它的所有子串分组,满足每组中两两之间互为子串包含关系,最小的组数。
给一个字符串。若干个询问,每次询问一个区间的答案。
\(n,q\le 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[l\dots r+1,r+1]\),由于\(len\)最小,不存在\(s[l+2\dots r+1,r+1]\)的子串。于是可以把\(s[l,r]\)替换成\(s[l,r+1]\)。
一直这样做下去,最终可以使长度相同。
结论2:如果最长反链长度\(\ge k\),当且仅当长度\(n-k+1\)的子串互不相同。
证明:充分性显然,接下来证必要性。
只考虑字符串长度相等的最长反链,设字符串长度为\(len\)。此时有\(n-len+1\ge 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-1\le n-k\)。于是对于\(len\)来说,本质不同的子串不超过\(n-len+1-((n-k)-(len-1)+1)=k-1\),矛盾。
推论:答案为:\(n-LCP_{i<j}(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(n\lg^2 n)\)。
题解给出个不错的想法:在后缀树上对endpos集合启发式合并。合并之后,新形成的邻居(即合并\(S_1,S_2\),\(x\in S_1,y\in S_2\),\(x,y\)在\(S_1\bigcup S_2\)中相邻)可能对答案产生贡献,于是记下二元组\((x,y)\)。根据启发式合并的性质知道这样的二元组有\(O(n\lg 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\n",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\n",ans[i]);
return 0;
}
标签:子串,20,int,isr,len,UR,fa,UOJ608,反链 来源: https://www.cnblogs.com/jz-597/p/14620160.html