POJ Milk Patterns(后缀数组和二分)

13 篇文章 0 订阅

这个题和昨天写的Musical Theme基本一样,那篇博客有后缀数组的学习,如果那个题没有写过的话,搞完了这个题可以去做一下。

题意

显然就是求最长可重复子串的长度,但是该子串重复的次数需不小于K。

思路

罗穗骞 《后缀数组——处理字符串的有力工具》
先求后缀树组,而后利用后缀树组求height数组(height[i]:存储排名为i和i-1的后缀子串的最长重复前缀子串长度 )。把排序后的后缀分成若干组,其中每组的后缀之间的height值都不小于k。例如,字符串为“aabaaaab”,当k=2时,后缀分成了4组如图所示
可以想到当一组内的height都不小于k时,我们只需判断组内包含的后缀数组的数量是否不小于K个就行了,如果满足,那么当长度等于k时也是满足条件的,因此我们需要对长度进行二分,然后判断是否可行即可

代码
#pragma GCC optimize(2)
//#include<bits/stdc++.h>
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;

typedef long long ll;
typedef unsigned long ul;
typedef unsigned long long ull;
#define pi acos(-1.0)
#define e exp(1.0)
#define pb push_back
#define mk make_pair
#define fir first
#define sec second
#define scf scanf
#define prf printf
typedef pair<ll,ll> pa;
const ll INF=0x3f3f3f3f3f3f3f3f;
const ll maxn=2e4+7;
const ll maxnum=1e6+7;
ll N,M,K;
ll sa[maxn],rank[maxn],r[maxn],height[maxn],tmp[maxn]; 
bool cmp(ll i,ll j){
	if(rank[i]!=rank[j])
	return rank[i]<rank[j];
	ll r1=i+K<=N?rank[i+K]:-1;
	ll r2=j+K<=N?rank[j+K]:-1;
	return r1<r2;
}
//求后缀数组 
void do_sa(){
	ll i,j;
	for(i=0;i<=N;i++){
		sa[i]=i;
		rank[sa[i]]=(i!=N?r[i]:-1);
	}
	for(K=1;K<=N;K<<=1){
		sort(sa,sa+1+N,cmp);
		tmp[sa[0]]=0;
		for(i=1;i<=N;i++)
		tmp[sa[i]]=tmp[sa[i-1]]+(cmp(sa[i-1],sa[i])?1:0);
		for(i=0;i<=N;i++)
		rank[i]=tmp[i];
	}
	return ;
}
void get_height(){
	ll i,j,k=0;
	//枚举每个后缀的起始位置 
	for(i=0;i<N;i++){
		if(k)
		k--;
		else
		k=0;
		j=sa[rank[i]-1];
		while(r[i+k]==r[j+k])
		k++;
		height[rank[i]]=k;//height[i]:存储排名为i和i-1的后缀子串的最长前缀子串长度 
	}
	return ;
	
}
bool check(ll mid){
	ll i,j,cnt=0;
	for(i=1;i<=N;i++){
		if(height[i]>=mid){
			cnt++;
			if(cnt+1>=M)
			return true;
		}
		else
		cnt=0;
	}
	return false;
}
int main()
{
//  freopen(".../.txt","w",stdout);
//  freopen(".../.txt","r",stdin);
	ios::sync_with_stdio(false);
	ll i,j,k;
	cin>>N>>M;
	for(i=0;i<N;i++)
	cin>>r[i];
	do_sa();
	get_height();
	ll L=0,R=N,mid,res=0;
	while(L<=R){
		mid=(R-L)/2+L;
		if(check(mid)){
			L=mid+1;
			res=max(res,mid);
		} 
		else{
			R=mid-1;
		}
	}
	cout<<res<<endl;
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值