[USACO06DEC]Milk Patterns G

题目链接:https://www.luogu.com.cn/problem/P2852

今天打算复习一下后缀数组
后缀数组裸题。
紧扣后缀数组性质, L C P ( s a [ i ] , s a [ j ] ) = m i n ( h e i g h t [ k ] ) k < i < = j LCP(sa[i],sa[j])=min(height[k]) \quad k<i<=j LCP(sa[i],sa[j])=min(height[k])k<i<=j
还有一个性质:任意一个后缀的前缀是原串中唯一的一个子串

然后就可以先预处理出后缀数组,然后二分长度.
检查的时候就扫一遍 h e i g h t height height数组,看看是否存在一段长度不少于 k k k的区间的 h e i g h t height height均不小于 l e n len len 即可

因为由前文后缀数组的性质:当 L C P ( s a [ l ] , s a [ r ] ) LCP(sa[l],sa[r]) LCP(sa[l],sa[r])大于等于 k k k时,区间 [ l , r ] 1 < = l , r < = n [l,r]\quad 1<=l,r<=n [l,r]1<=l,r<=n中每两个数的 L C P LCP LCP都大于等于 k k k

C o d e Code Code

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN = 2e4;
int str[MAXN + 10], sa[MAXN + 10], rk[MAXN + 10], id[MAXN + 10], height[MAXN + 10];
inline int read();

namespace SA{
	void Rsort(int, int);
	void get_sa(int);
	void get_height(int);
	int get_ans(int, int);
}
using namespace SA;

int main(){
	//freopen ("std.in", "r",stdin);
	//freopen ("std.out", "w", stdout);
	int n, k, len = 0;
	n = read(), k = read();
	for (register int i = 1; i <= n; ++i)	str[i] = read();
	get_sa(n);	get_height(n);
	int l = 1, r = n;
	while (l <= r){
		int mid = l + r >> 1;
		if (get_ans(n, mid) >= k)	l = (len = mid) + 1;
		else   r = mid - 1;
	}
	printf("%d\n", len);
	return 0;
}

inline int read(){
	int x = 0;
	char c = getchar();
	while (!isdigit(c))c = getchar();
	while (isdigit(c))x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	return x;
}

namespace SA{
	void Rsort(int n, int m){
		static int buck[MAXN + 10];
		for (register int i = 1; i <= m; ++i)	buck[i] = 0;
		for (register int i = 1; i <= n; ++i)	++buck[rk[i]];
		for (register int i = 1; i <= m; ++i)	buck[i] += buck[i - 1];
		for (register int i = n; i >= 1; --i)	sa[ buck[rk[id[i]]]-- ] = id[i];
	}
	
	void get_sa(int n){
		int m = 127, p = 0;
		for (register int i = 1; i <= n; ++i){
			rk[i] = str[i];
			id[i] = i;
		}
		Rsort(n, m);
		for (register int len = 1; p < n; m = p, len <<= 1){
			p = 0;
			for (register int i = 1; i <= len; ++i)	id[++p] = n - len + i;
			for (register int i = 1; i <= n; ++i)
				if (sa[i] > len)	id[++p] = sa[i] - len;
			Rsort(n, m);
			std::swap(rk, id);
			rk[sa[1]] = p = 1;
			for (register int i = 2; i <= n; ++i)
				rk[sa[i]] = (id[sa[i]] == id[sa[i - 1]] && id[sa[i] + len] == id[sa[i - 1] + len])? p : ++p;
		}
	}
	
	void get_height(int n){
		int k = 0;
		for (register int i = 1; i <= n; ++i){
			if (k)	--k;
			int j = sa[rk[i] - 1];
			while (str[i + k] == str[j + k])	++k;
			height[rk[i]] = k;
		}
	}
	
	int get_ans(int n, int len){
		int cnt = 1, l = 99999999, ans = 1;
		for (register int i = 2; i <= n; ++i){
			l = min(l, height[i]);
			if (l < len){
				ans = max(ans, cnt);
				cnt = 1;	l = 99999999;
			}
			else	++cnt;
		}
		ans = max(ans, cnt);
		return ans;
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值