其他分享
首页 > 其他分享> > UOJ608. 【UR #20】机器蚤分组

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