首页 > 其他分享 >BZOJ1717 Milk Patterns 产奶的模式 (二分+后缀数组+height数组)

BZOJ1717 Milk Patterns 产奶的模式 (二分+后缀数组+height数组)

时间:2024-01-19 21:25:18浏览次数:24  
标签:产奶 BZOJ1717 后缀 height int MAXN 数组 sa

发现这样起标题更能引流(ylg 实锤了)

题意

给定一个长度为 \(n\) 的数组 \(a\),求在 \(a\) 中出现了至少 \(k\) 次的最长子串的长度。

解法

考虑将一个子串拆成两个后缀,即 \([l,r]=[l,n]-[r,n]\),发现一个长度为 \(x\) 的子串 \(t\) 在 \(i,j\) 两个位置出现过当且仅当后缀 \(i,j\) 有共同前缀 \(x\)。

于是我们想到二分一个长度 \(x\),接下来的问题是,怎么判断是否有 \(k\) 个后缀,他们的公共前缀长度至少为 \(x\)。

于是我们想到了后缀数组的 \(height\) 数组,以下用 \(h_i\) 代表 \(height_i\),即 \(sa_i,sa_{i-1}\) 的最长公共前缀,如果你不知道什么是 \(height\) 数组,你可能需要用别的解法或者先去学习一下后缀数组。

考虑经典定理:\(lcp(sa_i,sa_j)=\min\limits_{p=l+1}^j h_p\),且对于任意一个区间 \([l,r]\) 来说,若有 \(\forall i \in [l+1,r],h_i\ge x\),那么显然 \(sa_l,sa_{l+1},\cdots,sa_r\) 的最长公共前缀的长度都不小于 \(x\)。于是,问题就被我们转换为,在 \(h\) 数组上是否存在连续的 \(k-1\) 个不小于 \(x\) 的数,显然二分即可解决。

这里使用了复杂度为 \(O(n\log^2n)\) 的 std::sort 求解后缀数组,复杂度即求解后缀数组的 \(O(n\log^2 n)\)。

const int MAXN = 2e4 + 5;
int n, k, a[MAXN], rk[MAXN], oldrk[MAXN], sa[MAXN], h[MAXN];

bool check(int x) {
	int cnt = 0;
	for (int i = 1; i <= n; ++i) {
		if (h[i] < x) cnt = 0;
		else cnt++;
		if (cnt == k - 1) return true;
	}
	return false;
}

void work() {
	cin >> n >> k;
	for (int i = 1; i <= n; ++i) cin >> a[i];
	for (int i = 1; i <= n; ++i) rk[i] = a[i], sa[i] = i;
	for (int _ = 0; (1ll << _) <= n; ++_) {
		const int w = 1ll << _;
		sort(sa + 1, sa + 1 + n, [&](const auto x, const auto y) {
			return rk[x] == rk[y] ? rk[x + w] < rk[y + w] : rk[x] < rk[y];
		});
		for (int i = 1; i <= n; ++i) oldrk[i] = rk[i];
		for (int p = 0, i = 1; i <= n; ++i) {
			if (oldrk[sa[i]] == oldrk[sa[i - 1]] && 
				oldrk[sa[i] + w] == oldrk[sa[i - 1] + w]) rk[sa[i]] = p;
			else rk[sa[i]] = ++p;
		}
	}
	for (int i = 1, p = 0; i <= n; ++i) {
		if (!rk[i]) continue;
		if (p) p--;
		while (a[i + p] == a[sa[rk[i] - 1] + p]) p++;
		h[rk[i]] = p;
	}
	int l = 0, r = n + 1;
	while (l + 1 < r) {
		if (check(mid)) l = mid;
		else r = mid;
	}
	cout << l << endl;
}

标签:产奶,BZOJ1717,后缀,height,int,MAXN,数组,sa
From: https://www.cnblogs.com/XiaoQuQu/p/17975645

相关文章

  • 912.排序数组--堆排序
    1.题目介绍给你一个整数数组nums,请你将该数组升序排列。示例1:输入:nums=[5,2,3,1]输出:[1,2,3,5]示例2:输入:nums=[5,1,1,2,0,0]输出:[0,0,1,1,2,5]2.题解2.1堆排序思路题目给了我们一个vector数组,要使用堆排序,我们首先要创建一个大根堆,再在这个大根堆的基础上对数......
  • nacos 动态刷新 数组对象 List/数组类型、复杂类对象配置
    @Value环境依赖版本SpringCloud是个大前提,不然还是考虑上面方式或者原生接入方案;@NacosPropertySource(dataId="mydata",autoRefreshed=true)同时@RefreshScope方能接收到nacos的push数据。@NacosValue依赖springbootNacos动态刷新基本数据类型很简单,只需要在字段......
  • 对象数组,根据字符串字段,并按默认方式排序
    sort在字符串的默认排序,是按unicode字节码排序的,一般字符串的排序可以通过strA.localeCompare(strB)来完成,但我这里必须要按字符串的默认方式排序。list=list.sort((a,b)=>{varjobA=a.Job;varjobB=b.Job;......
  • Leetcode 26 删除数组重复项
    题目描述给你一个非严格递增排列的数组nums,请你原地删除重复出现的元素,使每个元素只出现一次,返回删除后数组的新长度。元素的相对顺序应该保持一致。然后返回nums中唯一元素的个数。考虑nums的唯一元素的数量为k,你需要做以下事情确保你的题解可以被通过:更改数......
  • 数组的逆排序
    include<stdio.h>//实现函数初始化数组为全0voidInit(intarr[],intsz){inti=0;for(i=0;i<sz;i++){arr[i]=0;}}//打印数组的每个元素voidPrint(intarr[],intsz){inti=0;for(i=0;i<sz;i++){printf("%d",arr[i]);}printf(&qu......
  • 代码随想录 day23 修剪二叉搜索树 将有序数组转换为二叉搜索树 把二叉搜索树转换为累
    修剪二叉搜索树这道题不能直接写删除代码因为要涉及父子关系的保留如这样是暴力删掉不符合区间的节点但是没有保留父子关系这里我们把不符合区间的节点通过一个临时节点传递出来然后在外面合适方向接住具体怎么接住的呢其实就是对于root来说左边子树抛出的节点就会......
  • 912.排序数组--归并排序
    1.题目介绍给你一个整数数组nums,请你将该数组升序排列。示例1:输入:nums=[5,2,3,1]输出:[1,2,3,5]示例2:输入:nums=[5,1,1,2,0,0]输出:[0,0,1,1,2,5]2.题解2.1归并排序思路归并排序利用了分治的思想来对序列进行排序。对一个长为n的待排序的序列,我们将其分解成两个......
  • 数组的运用和方法的运用
    ##对reduce等基础的数组方法无法熟练组合运用constmovements=[200,450,-400,3000,-650,-130,70,1300];constlabelSumIn=document.querySelector('.summary**value--in');constlabelSumOut=document.querySelector('.summary**value--out');constl......
  • JavaScript数组使用方法
    constarr=[4,3,2,1];/*操作数组*///在末尾添加元素arr.push(5);//结果:[4,3,2,1,5]//在开头添加元素arr.unshift(0);//结果:[0,4,3,2,1,5]//移除最后一个元素arr.pop();//结果:[0,4,3,2,1]//移除第一个元素arr.shift();//结果:[4,3,2......
  • 数组篇-其之一-数组的概念与一维数组
    本文中使用到的工具是IntellijIDEA和JDK8,需要安装两款工具的小伙伴请查看这两篇教程:点我查看安装JDK8教程、点我查看安装IntellijIDEA教程。假设我想在某宝上买一点零食(没错,我承认我确实是个吃货),经过搜索后出现了如下结果,我们发现每一项都包含相同内容:图片、标题、价格、购......