poj3261 后缀数组(模板)

本文介绍如何使用后缀数组解决子串频次问题,详细解释了后缀数组的定义及其应用,包括rank、sa、h数组的含义,以及如何通过数列分块方法求解连续k-1最小值的最大值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原题:http://poj.org/problem?id=3261

题解:求出现至少k次子串。后缀数组可以处理子串的问题。后缀数组主要的定义如下。

rank[i] 表示第i个后缀的排名
sa[i]   表示排名为i的哪个后缀
h[i]    表示排名为i与i+1的最长公共前缀

排名指字典序,可以用基数排序,倍增预处理上面3个数组。

出现了K次的子串长度就是h数组连续k-1的最小值,我们要求连续k-1的最小值的最大值,可以枚举左端点求RMQ,求区间最小值有很多方法,这道题可以用ST表,或数列分块的方法,这里用的是数列分块的方法。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=22000;
const int M=1e6+10;
int n,m,kk,s[N],suf[N],sa[N],pos[N],rank[N<<1]={0},rank1[N<<1]={0},cnt[M],tmp[M],t[M];
int h[N],v[1100];
inline int query(int l,int r){
	int ans=1e18;
	for(int i=l;i<=min(r,pos[l]*m);i++) ans=min(ans,h[i]);
	if(pos[l] != pos[r])     
		for(int i=(pos[r]-1)*m+1;i<=r;i++)ans=min(ans,h[i]);
	for(int i=pos[l]+1;i<=pos[r]-1;i++) ans=min(ans,v[i]);
	return ans;
}
int main(){
	freopen("poj3261.in","r",stdin);
	scanf("%d %d",&n,&kk);
	for(int i=1;i<=n;i++) scanf("%d",&s[i]);
	s[0]=-1;s[n+1]=-1;
	//初始化 rank[1],首字母排名 
	//以经确定的名次 
	for(int i=1;i<=n;i++) t[ s[i] ]++;
	for(int i=0;i<=1e6;i++) t[i]+=t[i-1];
	for(int i=1;i<=n;i++) rank[i]=t[s[i]];	
	for(int p=1,k=0; k!=n ; p<<=1){
		//对第一关键字排序
		memset(cnt,0,sizeof cnt);
		for(int i=1;i<=n;i++) cnt[ rank[i+p] ]++; 
		for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1]; 
		for(int i=n;i>=1;i--) tmp[cnt[rank[i+p]]--]=i;//收集 
		
		//对第二关键字排序 
		memset(cnt,0,sizeof cnt);
		for(int i=1;i<=n;i++) cnt[rank[i]]++;
		for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1];
		for(int i=n;i>=1;i--) sa[cnt[rank[tmp[i]]]--]=tmp[i];
		//通过sa求新的rank 
		memcpy(rank1,rank,sizeof(rank)>>1);
		k=1;
		rank[ sa[1] ]=1;
		for(int i=2;i<=n;i++){
			if(rank1[ sa[i] ] != rank1[sa[i-1]] || rank1[sa[i]]==rank1[sa[i-1]] && rank1[sa[i]+p] != rank1[sa[i-1]+p]) k++;
			rank[ sa[i] ]=k;
		}
		
	}
	//求height i,i+1
	for(int i=1,k=1;i<=n;i++){
		if(rank[i]==n) {
			h[rank[i]]=0;continue;
		}
		k--;if(k<0)k=0;
		while(s[i+k]==s[ sa[rank[i]+1]+k]  )k++;
		h[rank[i]]=k;
	}
	//分块的预处理 
	m=sqrt(n);
	for(int i=1;i<=n;i++) pos[i]=(i-1)/m+1;
	for(int i=1;i<=pos[n];i++) v[i]=1e18;
	for(int i=1;i<=n;i++) v[ pos[i] ]=min(v[pos[i]],h[i]);

	int L=kk-1;int ans=0;
	for(int i=1;i<=n-L+1;i++){
		int x=i;int y=x+L-1;
		ans=max(ans,query(x,y));
	}
	printf("%d\n",ans);		
	return 0;
} 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值