码迷,mamicode.com
首页 > 其他好文 > 详细

[POJ1743]Musical Theme

时间:2018-03-03 19:27:33      阅读:192      评论:0      收藏:0      [点我收藏+]

标签:main   为我   之间   公共前缀   题意   多少   有序   情况   body   

题意:给一个数字串,求不可重叠的相似子串,两个子串$a,b$相似的定义是$a_i-b_i$都相等

昨晚二爷讲课,去膜拜一发,学了一下不知道学了多少次也没懂的后缀数组

后缀数组能将字符串$S$的所有后缀$S_{i\cdots n}=\text{Suffix}(i)$排序,$sa_i$表示排第$i$名的后缀的开头下标,$rank_i$表示$\text{Suffix}(i)$的排名,$height_i$表示排第$i$名的后缀和排第$i-1$名的后缀的最长公共前缀长度

考虑用倍增求$rank_i$,一开始把所有长度为$1$的字符串排序,然后排序长度为$2$的字符串,以此类推

假设当前要排所有长度为$L$的字符串,因为我们已经知道了所有长度为$\dfrac L2$的字符串的相对大小,所以我们把每个长度为$L$的字符串看成由两个长度为$\dfrac L2$的字符串拼起来的东西,相当于用(长度为$\dfrac L2$的字符串的相对排名)构成的二元组来表示每一个长度为$L$的字符串

技术分享图片

原理很简单,就是用长度为$\dfrac L2$的字符串之间的关系得出长度为$L$之间的字符串的关系

容易看出当$L=1$时字符串大小就是字母大小,之后每次用上一次得出的$rank$组成二元组并排序

注意到我们是对$rank$组成的二元组排序,这意味着被排序的数$\leq n$,所以我们可以使用计数排序,代码挺好写的

int cnt[N],tmp[N];
void sort(int*x){
	int i,m=0;
	for(i=1;i<=n;i++){
		m=max(m,x[i]);
		cnt[x[i]]++;
	}
	for(i=1;i<=m;i++)x[i]+=x[i-1];
	for(i=n;i>0;i--)tmp[cnt[x[i]]--]=x[i];
	for(i=1;i<=n;i++)x[i]=tmp[i];
}

计数排序的原理是统计“有多少个数小于等于这个数”,这里的自减符号用于处理元素相同的情况,倒过来处理保证了相等元素之间的相对顺序不会改变(每处理完一个元素,下次碰到这个元素时它的下标已经减去了$1$)

对二元组的排序也容易解决,先对第二维排序,再对第一维排序(因为计数排序是稳定排序,当排完后第一维相等的连续段的第二维是有序的)

于是我们成功求出了$rank_i$,那么$sa_i$也易于求得

我们还要求$height_i$,有一个关于它的结论是$height_{rank_i}\geq height_{rank_{i-1}}-1$,证明如下(来自许智磊的论文

以下证明比较绕,请熟悉$sa,rank,height$的概念再看证明

当$height_{rank_{i-1}}\leq1$,显然成立

当$height_{rank_{i-1}}\geq2$,先证一堆奇奇怪怪的东西

设$lcp(i,j)=lcp(\text{Suffix}(sa_i),\text{Suffix}(sa_j))(i\leq j)$,它等价于$lcp(\text{Suffix}({sa_{i\cdots j}}))$

因为更多串取最长公共前缀不会让答案变大,所以如果$k\geq j\geq i$,那么$lcp(k,j)\geq lcp(k,i)$

设$k=sa_{rank_{i-1}-1}$,即$\text{Suffix}(k)$是$\text{Suffix}(i-1)$的前一位

根据$lcp(\text{Suffix}(k),\text{Suffix}(i-1))=height_{rank_{i-1}}\geq2$,两边同时向右偏移$1$位,最长公共前缀$-1$,所以$lcp(\text{Suffix}(k+1),\text{Suffix}(i))=height_{rank_{i-1}}-1$

因为$rank_{k}\lt rank_{i-1}$且$height_{rank_{i-1}}\geq2$,所以不等式两边所代表的后缀第一位相同,右移一位不影响比较结果,推出$rank_{k+1}\lt rank_i$,也就是$rank_{k+1}\leq rank_i-1$

因为$rank_i\geq rank_i-1\geq rank_{k+1}$,所以$lcp(rank_i,rank_i-1)\geq lcp(rank_i,rank_{k+1})=lcp(\text{Suffix}(i),\text{Suffix}(k+1))=height_{rank_{i-1}}-1$

最后,$height_{rank_i}=lcp(rank_i,rank_i-1)\geq height_{rank_{i-1}}-1$,于是就证完了

如果按$height_{rank_{1\cdots n}}$这样的顺序来求,那么它每次先$-1$,然后就只可能增加,这保证了整个求$height$的过程是$O(n)$的

算$height_i$的代码一定要记好,考场上没时间来证这种鬼定理

所以我们求出了后缀数组中用处最大的东西:$height_i$,下面用它做题

题目说“相似”,假如对原序列差分,那么问题就变成了不可重叠最长重复子串

考虑二分,假设当前二分的答案是$k$,如果存在$[l,r]$使任意$i\in[l,r]$都有$height_i\geq k$,那么这些后缀的$lcp$长度就$\geq k$,我们可以贪心地选取两个相距最远的后缀,也就是说如果$\max\left\{sa_{l\cdots r}\right\}-\min\left\{sa_{l\cdots r}\right\}\geq k$,那么就存在两个不重叠的长度为$k$的子串

因为差分了,所以最后答案应该$+1$

于是这题就做完了,唉终于填了一个看上去不可填的坑...

为什么我写的后缀数组常数这么鬼大>_<

#include<stdio.h>
#include<string.h>
int s[100010],rk[200010],sa[100010],cnt[100010],id[100010],height[100010],n;
struct pr{
	int c[2],id;
	pr(int a=0,int b=0,int d=0){c[0]=a;c[1]=b;id=d;}
}p[100010],q[100010];
bool operator!=(pr a,pr b){return(a.c[0]!=b.c[0])||(a.c[1]!=b.c[1]);}
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}
void sort(int f){
	int i,m;
	memset(cnt,0,sizeof(cnt));
	m=0;
	for(i=1;i<=n;i++){
		cnt[p[i].c[f]]++;
		m=max(m,p[i].c[f]);
	}
	for(i=1;i<=m;i++)cnt[i]+=cnt[i-1];
	for(i=n;i>0;i--)q[cnt[p[i].c[f]]--]=p[i];
	for(i=1;i<=n;i++)p[i]=q[i];
}
void suf(){
	int i,l,m;
	memset(rk,0,sizeof(rk));
	for(i=1;i<=n;i++)rk[i]=s[i];
	for(l=1;l<=n;l<<=1){
		for(i=1;i<=n;i++)p[i]=pr(rk[i],rk[i+l],i);
		sort(1);
		sort(0);
		m=0;
		for(i=1;i<=n;i++){
			if(p[i]!=p[i-1])m++;
			rk[p[i].id]=m;
		}
	}
	for(i=1;i<=n;i++)sa[rk[i]]=i;
	l=0;
	for(i=1;i<=n;i++){
		if(l)l--;
		while(s[i+l]==s[sa[rk[i]-1]+l])l++;
		height[rk[i]]=l;
	}
}
bool check(int k){
	int i,mx,mn;
	for(i=1;i<=n;i++){
		if(height[i]>=k){
			mx=max(mx,max(sa[i-1],sa[i]));
			mn=min(mn,min(sa[i-1],sa[i]));
		}else{
			mx=-1;
			mn=n;
		}
		if(mx-mn>=k)return 1;
	}
	return 0;
}
int main(){
	int i,l,r,mid,ans;
	while(1){
		scanf("%d",&n);
		if(n==0)break;
		for(i=1;i<=n;i++)scanf("%d",s+i);
		n--;
		for(i=1;i<=n;i++)s[i]=s[i+1]-s[i]+88;
		suf();
		l=1;
		r=n>>1;
		ans=0;
		while(l<=r){
			mid=(l+r)>>1;
			if(check(mid)){
				ans=mid;
				l=mid+1;
			}else
				r=mid-1;
		}
		printf("%d\n",(ans<4)?0:(ans+1));
	}
}

[POJ1743]Musical Theme

标签:main   为我   之间   公共前缀   题意   多少   有序   情况   body   

原文地址:https://www.cnblogs.com/jefflyy/p/8497220.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!