九月二十六,题目就要与众不同

本文介绍pytest测试框架的使用方法及图像归一化的处理技巧。pytest支持灵活的测试用例编写规则,并介绍了fixture的作用域配置。同时,详细解释了图像归一化的原理与实现方法,展示了归一化前后像素值的变化及其对神经网络的影响。

1.pytest 使用介绍

pytest是一个非常成熟的全功能的Python测试框架

编写规则

编写pytest测试样例非常简单,只需要按照下面的规则:

测试文件以test_开头(以_test结尾也可以)
测试类以Test开头,并且不能带有 init 方法
测试函数以test_开头
断言使用基本的assert即可

fixture的scope参数

scope参数有四种,分别是’function’,‘module’,‘class’,‘session’,默认为function。

function:每个test都运行,默认是function的scope
class:每个class的所有test只运行一次
module:每个module的所有test只运行一次
session:每个session只运行一次

如何执行

  • pytest # run all tests below current dir
  • pytest test_mod.py # run tests in module file test_mod.py
  • pytest somepath # run all tests below somepath like ./tests/
  • pytest -k stringexpr # only run tests with names that match the
    the “string expression”, e.g. “MyClass and not method”
    will select TestMyClass.test_something
    but not TestMyClass.test_method_simple
  • pytest test_mod.py::test_func # only run tests that match the “node ID”,
    e.g “test_mod.py::test_func” will be selected
    only run test_func in test_mod.py

ResNet-18

这里的18指定带权重的18层,包括卷积层和全联接层,不包括池化层和BN层。

图片归一化

在这里插入图片描述
图像归一化最常见的就是最大最小值归一化方法,公式如下:

在这里插入图片描述
在这里插入图片描述
基于OpenCV实现图像最大最小值归一化的代码演示如下:

在这里插入图片描述

基于OpenCV实现图像最大最小值归一化的代码演示如下:

原图像素值输出

在这里插入图片描述
归一化之后像素值:

在这里插入图片描述

解释

原图与归一化之后的运行结果完全一致,说明归一化不会改变图像本身的信息存储,但是通过打印出来的像素值可以发现,取值范围从0~255已经转化为0~1之间了,这个对于后续的神经网络或者卷积神经网络处理有很大的好处,tensorflow官方给出mnist数据集,全部采用了归一化之后的结果作为输入图像数据来演示神经网络与卷积神经网络。

对图像进行归一化的两种形式

  1. img = image/255.0
  2. img = image/127.5 - 1

这两种对图像处理方式都是进行归一化,

1、归一化的范围为【0,1】

还原:img * 255.0

2、范围为【-1,1】

还原:(img + 1.) * 127.5

**感悟:**仅文章一句话,却让我看了一天~

下载方式:https://pan.quark.cn/s/a4b39357ea24 在纺织制造领域中,纱线的品质水平对最终制成品的整体质量具有决定性作用。 鉴于消费者对于产品规格和样式要求的不断变化,纺织制造工艺的执行过程日益呈现为一种更为复杂的操作体系,进而导致对纱线质量进行预测的任务变得更加困难。 在多预测技术中,传统的预测手段在面对多变量间相互交织的复杂关系时,往往显得力不从心。 因此,智能计算技术在预测纱线质量的应用场景中逐渐占据核心地位,其中人工神经网络凭借其卓越的非线性映射特性以及自适应学习机制,成为了多预测方法中的一种重要选择。 在智能计算技术的范畴内,粒子群优化算法(PSO)和反向传播神经网络(BP神经网络)是两种被广泛采用的技术方案。 粒子群优化算法是一种基于群体智能理念的优化技术,它通过模拟鸟类的群体觅食行为来寻求最优解,该算法因其操作简便、执行高效以及具备优秀的全局搜索性能,在函数优化、神经网络训练等多个领域得到了普遍应用。 反向传播神经网络则是一种由多层节点构成的前馈神经网络,它通过误差反向传播的机制来实现网络权重和阈值的动态调整,从而达成学习预测的目标。 在实际操作层面,反向传播神经网络因其架构设计简洁、实现过程便捷,因此被广泛部署于各类预测和分类任务之中。 然而,该方法也存在一些固有的局限性,例如容易陷入局部最优状态、网络收敛过程缓慢等问题。 而粒子群优化算法在参神经网络优化时,能够显著增强神经网络的全局搜索性能并提升收敛速度,有效规避神经网络陷入局部最优的困境。 将粒子群优化算法反向传播神经网络相结合形成的PSO-BP神经网络,通过运用粒子群优化算法对反向传播神经网络的权值和阈值进行精细化调整,能够在预测纱线断裂强度方面,显著提升预测结果的...
植物实例分割数据集 一、基础信息 数据集名称:植物实例分割数据集 图片数量: - 训练集:9,600张图片 - 验证集:913张图片 - 测试集:455张图片 总计:10,968张图片 分类类别:59个类别,对应数字标签0至58,涵盖多种植物状态或特征。 标注格式:YOLO格式,适用于实例分割任务,包含多边形标注点。 数据格式:图像文件,来源于植物图像数据库,适用于计算机视觉任务。 二、适用场景 • 农业植物监测AI系统开发:数据集支持实例分割任务,帮助构建能够自动识别植物特定区域并分类的AI模型,辅助农业专家进行精准监测和分析。 • 智能农业应用研发:集成至农业管理平台,提供实时植物状态识别功能,为作物健康管理和优化种植提供数据支持。 • 学术研究农业创新:支持植物科学人工智能交叉领域的研究,助力发表高水平农业AI论文。 • 农业教育培训:数据集可用于农业院校或培训机构,作为学生学习植物图像分析和实例分割技术的重要资源。 三、数据集优势 • 精准标注多样性:标注采用YOLO格式,确保分割区域定位精确;包含59个类别,覆盖多种植物状态,具有高度多样性。 • 数据量丰富:拥有超过10,000张图像,大规模数据支持模型充分学习和泛化。 • 任务适配性强:标注兼容主流深度学习框架(如YOLO、Mask R-CNN等),可直接用于实例分割任务,并可能扩展到目标检测或分类等任务。
室内物体实例分割数据集 一、基础信息 • 数据集名称:室内物体实例分割数据集 • 图片数量: 训练集:4923张图片 验证集:3926张图片 测试集:985张图片 总计:9834张图片 • 训练集:4923张图片 • 验证集:3926张图片 • 测试集:985张图片 • 总计:9834张图片 • 分类类别: 床 椅子 沙发 灭火器 人 盆栽植物 冰箱 桌子 垃圾桶 电视 • 床 • 椅子 • 沙发 • 灭火器 • 人 • 盆栽植物 • 冰箱 • 桌子 • 垃圾桶 • 电视 • 标注格式:YOLO格式,包含实例分割的多边形标注,适用于实例分割任务。 • 数据格式:图片为常见格式如JPEG或PNG。 二、适用场景 • 实例分割模型开发:适用于训练和评估实例分割AI模型,用于精确识别和分割室内环境中的物体,如家具、电器和人物。 • 智能家居物联网:可集成到智能家居系统中,实现自动物体检测和场景理解,提升家居自动化水平。 • 机器人导航交互:支持机器人在室内环境中的物体识别、避障和交互任务,增强机器人智能化应用。 • 学术研究教育:用于计算机视觉领域实例分割算法的研究教学,助力AI模型创新验证。 三、数据集优势 • 类别多样性:涵盖10个常见室内物体类别,包括家具、电器、人物和日常物品,提升模型在多样化场景中的泛化能力。 • 精确标注质量:采用YOLO格式的多边形标注,确保实例分割边界的准确性,适用于精细的物体识别任务。 • 数据规模充足:提供近万张标注图片,满足模型训练、验证和测试的需求,支持稳健的AI开发。 • 任务适配性强:标注格式兼容主流深度学习框架(如YOLO系列),便于快速集成到实例分割项目中,提高开发效率。
<think>嗯,我现在得解决这个题目题目是说,给定一个数组,然后有很多查询,每个查询问区间[L, R]中的最长完美序列的长度。完美序列就是连续的子序列,里面的元素互不相同。我得想办法高效地处理这些查询,因为数据量很大,N和M都是2e5级别,普通的暴力方法肯定超时。 首先,我需要想到一个高效的算法。常规的滑动窗口可能不行,因为每次查询的区间不同,滑动窗口是针对整个数组的最长无重复子数组的问题。但这里是任意区间的查询,所以需要预处理一些信息。 那,ST表可能是一个方向。因为ST表可以处理区间查询的问题,比如最大值、最小值之类的。那如何将这个最长完美序列的问题转化为可以用ST表处理的形式呢? 可能需要预处理每个位置i,作为右端点时,向左能延伸的最远左端点,这样在区间查询的时候,可以找到所有可能的窗口,然后取最大的长度。不过这可能还是不够高效,因为每个查询可能需要遍历很多可能的窗口。 或者,想到双指针法预处理每个位置i的最左边不重复的位置。例如,维护一个last数组,last[i]表示以i为右端点的最长无重复子数组的左端点。这个可以通过滑动窗口预处理得到。然后,对于任意查询区间[L, R],需要找到其中的最大窗口,该窗口必须完全包含在[L, R]中。 但这样的话,如何快速查询区间内的最大窗口长度呢?可能需要用某种数据结构,比如线段树或者ST表,保存每个区间内的最大窗口长度。这可能可行。例如,预处理每个位置i对应的窗口长度是i - last[i] +1,然后建立ST表来查询区间中的最大值。但这似乎有问题,因为某个窗口可能跨越了查询的L或者R边界。例如,一个窗口的左端点可能小于L,或者右端点可能大于R。所以直接取最大值可能不正确。 那这时候该怎么办呢?可能需要对于给定的查询[L, R],找到所有可能的窗口的起始点和结束点,其中窗口必须完全包含在[L, R]中。然后在这些窗口中找最大的长度。这似乎比较困难,因为每个窗口的长度可能不同,且预处理的信息可能无法直接应用。 另一种思路是,对于每个位置i,预处理出以i为右端点的最长无重复子数组的左端点left[i]。这样,对于查询[L, R],我们需要找到最大的j - L +1,其中j在[L, R]之间,并且left[j] <= L。或者类似的情况。或者,对于每个i,如果left[i] >= L,那么该窗口的有效长度是i - left[i] +1,否则是i - L +1。然后取所有这些的最大值。这可能可行,但如何高效计算呢? 这可能需要对于每个查询,遍历每个i从L到R,计算对应的有效长度,然后取最大值。这样的复杂度是O(M*N),对于2e5次查询来说,肯定会超时。 那这说明必须有一个更高效的方法,比如分块或者莫队算法?或者需要更巧妙的预处理方式。 想到另一种预处理方法:类似求每个位置i的最长无重复子数组的左端点left[i],然后用滑动窗口的方式预处理出这些left数组。这一步可以用哈希表来记录最近的重复元素的位置。例如,维护一个哈希表记录每个元素最后出现的位置,然后left[i] = max(left[i-1], last[a[i]] +1)。这样,left数组的预处理可以在O(N)的时间完成。例如: 初始化哈希表,left数组,left指针初始为0。遍历每个i,从0到n-1: left[i] = max(left[i-1], last[a[i]] +1) 然后更新last[a[i]] = i 这样,对于每个i,以i结尾的最长无重复子数组的左端点是left[i]。 然后,对于查询区间[L, R],想要找到最大的j-i+1,其中i >= L,j <= R,并且对于每个j,i >= left[j]。也就是说,在区间L到R中,每个j对应的可能的最长无重复子数组的左端点是left[j],所以该子数组的范围是[left[j], j]。如果这个范围完全包含在[L, R]中,那么长度是j - left[j] +1。否则,比如left[j] < L,那么有效的左端点只能是L,所以有效长度是j - L +1。这样,对于每个j,在该查询中的有效长度是min(j, R) - max(left[j], L) +1。然后取所有j的有效长度的最大值。 但是,这样的话,对于每个查询,我们需要遍历所有j从L到R,计算这个值,然后取最大。这的复杂度是O(M*N),显然不行。 那必须找到一种方法,能够预处理某些结构,使得可以在O(1)或O(logN)的时间内得到每个查询的结果。 这里想到,可以使用类似滑动窗口中的方法,但无法直接处理任意区间。所以可能需要结合二分或者ST表的结构。 或者,将每个位置j的有效窗口在查询中的贡献分为两种情况: 1. left[j] >= L:此时有效窗口长度是j - left[j] +1,只要j <= R。 2. left[j] < L:此时有效窗口长度是j - L +1,且要求该窗口中的元素在L到j之间都不重复。这可能需要保证在L到j之间没有重复元素。这时候,这样的窗口的左端点是L,右端点j的最大可能位置是某个位置,比如从L出发,向右扩展直到出现重复元素。 所以,对于情况2,最大的窗口可能出现在某个位置,例如从L开始的最长无重复子数组的右端点。这可能可以预处理每个L对应的最远右端点。例如,预处理一个数组right[L],表示从L开始的最长无重复子数组的右端点。这样,当查询区间包含L时,可以取min(right[L], R) - L +1作为候选值。 这样,对于一个查询[L, R],最大可能的值是两种情况中的最大值: a) 在区间中的某些j满足left[j] >= L,此时最大j - left[j] +1的值。 b) 在区间中,从L开始的最长无重复子数组,右端点不超过R时的长度。 同样,可能还有从R往左的情况,比如以R结尾的最长子数组的左端点是否在L的右边?或者,是否还有其他情况需要考虑? 不过,情况a的预处理可能可以用ST表来处理。因为每个j的j - left[j] +1是一个固定的值,可以预处理区间最大值的ST表。这样,查询[L, R]的a情况的最大值就是ST表中L到R的最大值。但是,这要求对应的left[j] >= L吗?不,不一定。所以直接取最大值可能不正确。 例如,假设某个j的left[j] < L,那么对应的窗口长度j - left[j] +1可能很大,但是该窗口的左端点可能小于L,所以实际有效长度是j - L +1。但这时候,直接取这个窗口的长度作为候选可能错误,因为原来的j-left[j]+1可能比j-L+1大,但实际有效长度是j-L+1,可能更小。所以这种情况下,原来的窗口的长度比实际的有效长度大,但此时如果直接取原来的最大值的话,会得到错误的结果。 所以,这说明情况a的预处理不能直接使用原来的窗口长度,而是需要取有效长度,即max(left[j], L)到j的长度。这可能无法直接通过预处理得到。 这似乎变得很复杂。那有没有更好的方法? 这个时候可能需要参考类似的历史题目或者算法。比如,这个问题和区间最长不重复子串的查询有关。这时候可以想到,这个问题的解法可能需要结合双指针预处理,并利用二分或者ST表来快速查询。 例如,预处理每个位置i的left[i],如前所述。同时,预处理每个i的右边最近的重复元素的位置,或者类似的信息。然后,对于每个查询[L, R],最大可能的长度是两种情况中的最大值: 1. 从某个i开始,在L到R的范围内,找到最大的j,使得left[j] <= i,并且j尽可能大。这可能类似于区间中的滑动窗口的最大长度。 或者,我们可以将问题转化为,对于每个查询[L, R],找到最大的k,使得存在某个区间[i, j],其中L <=i <= j <= R,且j-i+1=k,且该区间内的元素都是唯一的。这k的最大值即为答案。 那么,如何快速找到这个k? 可能,这个问题需要离线处理,比如利用莫队算法。但莫队算法的时间复杂度是O(N√M),对于2e5的M来说,可能无法通过。 所以,必须找到一个线性或O(N log N)的预处理方法。 这时候,想到一个可能的方法:预处理每个位置i,以及以i为结尾的最长无重复子数组的起始位置left[i]。然后,构建一个数组len[i] = i - left[i] +1,表示以i结尾的最长无重复子数组的长度。对于每个查询[L, R],答案可能有两种情况: a) 该最长子数组完全包含在查询区间中,即left[i] >= L,i <= R。此时,len[i]是一个候选值。 b) 该最长子数组部分在查询区间外,即left[i] < L。此时,有效长度是i - L +1,并且需要确保在区间[L, i]中没有重复元素。 对于情况a,我们需要在查询区间中找到最大的len[i],其中left[i] >= L,并且i <= R。这可能可以通过预处理一个ST表,存储len数组的最大值,然后在查询时,找到[L, R]区间内的最大值,并且对应的left[i] >= L。但是这似乎很难,因为要同时满足两个条件:i在[L, R]区间,且left[i] >= L。这可能需要用到二维的预处理结构,但这是不现实的。 或者,我们可以对每个位置i,预处理当left[i] >= L时,最大的len[i]是多少。这显然无法直接预处理,因为L是变化的。 这时候,可能需要另辟蹊径。例如,对于每个查询,最大的可能答案要么是某个完全包含在[L, R]中的最长无重复子数组,要么是某个跨越L或者 R的子数组。但具体如何呢? 或者,可以采用类似“双指针”的方式,预处理两个数组: - 对于每个位置i,预处理left[i]如前所述,即最左的不重复的位置。 - 另一个数组right[i],表示从i开始向右扩展的最远位置,使得i到right[i]之间的元素都是唯一的。 这样,预处理right数组可能需要O(n)的时间。 然后,对于查询[L, R],可能的候选答案有三个来源: 1. 完全包含在[L, R]中的某个最长子数组,即其left[i] >= L,且i <= R。此时的最大len[i]。 2. 以L为起点的最长子数组,即right[L],但不超过R。此时长度为min(right[L], R) - L +1。 3. 以R为终点的最长子数组,但其左端点可能小于L。此时有效长度为R - max(left[R], L) +1。 然后,取这三个情况的最大值作为答案。 但这样是否正确呢?或者说是否还有其他情况需要考虑? 例如,可能存在某个中间位置i,其left[i] < L,但i到L之间的元素都是唯一的,那么此时的有效长度为i - L +1。这可能没有被上述三种情况覆盖。 比如,假设L=2,i=5,并且从2到5的元素都是唯一的,那么有效长度是4。这属于情况2吗?情况2是处理以L为起点的最长子数组,这可能覆盖这种情况吗? 比如,假设right[L]是最大的可能j,使得从L到j的元素都不重复。那么,在这个例子中,right[L]应该>=5。所以,情况2中的min(right[L], R) - L +1 就等于5-2+1=4。这时候,情况2就能覆盖这种情况。 那这样,情况2可能已经包含了所有以L为起点的可能的子数组的最大长度。同理,情况3可能处理以R为终点的子数组的有效长度。而情况1处理的是中间的那些子数组,其左右端点都在查询区间内,并且他们的左端点不小于L。 这时候,剩下的问题是如何高效计算情况1中的最大值。 对于情况1,我们需要找到在区间[L, R]中,满足left[i] >= L的所有i,然后取最大的len[i]。这相当于在数组len中,查询区间[L, R]内满足left[i] >= L的最大值。这显然无法直接用ST表来处理,因为条件left[i] >= L是动态变化的,取决于查询的L。 这时候,可能需要将问题转换为:对于每个i,只有当left[i] >= L时,len[i]才可能被考虑。那么,如何快速找到满足i >= L、i <= R、left[i] >= L的最大len[i]? 这可能需要对每个i,预处理left数组,然后对于每个查询L,找到在区间i >= L,i <= R,且left[i] >= L的i的最大len[i]。 这似乎需要一种可以快速查询区间内满足某个条件的最大值的数据结构。而这样的结构可能需要离线处理或者分块。 比如,可以将数组分块,每个块预处理不同的信息。但对于2e5次查询来说,这可能不够高效。 或者,可以离线处理所有查询,按L排序,然后利用指针移动的方式,维护某种结构。例如,当处理一个查询L时,所有i >= L且left[i] >= L的可能i会被考虑。这可能比较复杂。 这似乎变得非常困难。于是,我需要重新思考这个问题,有没有其他思路? 回到问题本身,假设我们已经预处理了left数组。对于每个查询[L, R],最大长度可能出现在两种情况: 情况一:完全在查询区间中的某个窗口,其left[i] >= L。此时的最大长度为max_len1 = max{ i - left[i] +1 | L <= i <= R, left[i] >= L } 情况二:某个窗口的左端点小于L,但右端点位于查询区间中。此时的有效长度为i - L +1,并且在该窗口[L, i]中的元素都不重复。此时的最大长度是max_len2 = max{ i - L +1 | L <=i <= R,且在[L, i]中没有重复元素 } 那么,整个答案就是max(max_len1, max_len2) 现在的问题是如何高效计算max_len1和max_len2。 对于max_len1,由于i的范围是[L, R],而left[i] >= L的条件,可以转化为left[i] >= L。这时候,max_len1等于max{ len[i] | L <=i <= R, left[i] >= L } 这里的len[i] = i - left[i] +1。所以,我们需要找到在i的区间[L, R]内,left[i] >= L的情况下,len[i]的最大值。这可能需要用某种数据结构快速查询。 然而,这样的条件查询并不容易处理。因为left[i]的值是随i变化的,而L是每个查询的参数。这时候,可能需要对每个i,预处理一些结构,比如对于每个可能的L,找到对应的i的范围。这显然不可行。 那是否有其他方法? 例如,可以注意到,left[i]是单调递增的。因为,在预处理left数组的时候,left[i] = max(left[i-1], last[a[i]] +1)。而由于last[a[i]]的更新,可能导致left[i] >= left[i-1]。例如,当当前元素a[i]在之前出现过,那么left[i]就会是上一个出现位置+1,或者left[i-1]中的较大者。所以,整个left数组是单调不减的。 假设left数组确实是单调不减的,那么对于每个查询的L,要找到最大的i <= R,且left[i] >= L。这可能可以通过二分查找来找到最大的i满足left[i] >= L,并且i <= R。然后,在该i的左边或右边可能存在更大的len值? 或者,假设left数组是单调不减的,那么对于某个i,如果left[i] >= L,则对于所有j >=i,left[j] >= left[i] >= L。因此,在i到R之间,所有的j都满足left[j] >= L。所以,此时max_len1是这些j的len[j]中的最大值。因此,如果left数组是单调不减的,我们可以通过二分找到第一个i的位置,使得left[i] >= L,并且在i到R之间取最大的len[j]。 这时,在i到R之间的所有j的left[j] >= L,所以他们的len[j]是有效的。因此,max_len1就是该区间内的最大值。而i到R的区间可以用ST表预处理最大值。 同时,对于i左边的j,可能left[j] < L,所以他们的len[j]无效。所以,max_len1等于ST表查询区间[i, R]的最大值。 这时候,问题转化为,如何找到第一个i >= L,使得left[i] >= L。因为left数组是单调不减的,所以一旦找到这个i,后面的所有j >=i都满足left[j] >= L。因此,可以用二分查找来找到这个i。 例如,在数组left中,找到最小的i >= L,使得left[i] >= L。然后,如果i > R的话,说明没有这样的i,max_len1为0。否则,max_len1是ST表查询区间[i, R]的最大值。 同时,对于情况二,max_len2等于最大的j,其中j在[L, R]之间,并且从L到j没有重复元素。这相当于,在预处理中,对于每个位置L,找到最大的k,使得区间[L, k]中的元素没有重复。这可以用双指针预处理一个数组far[L],表示从L出发的最远右端点。例如,far[L]表示最大的k,使得[L, k]中的元素都是唯一的。这样,max_len2就是min(far[L], R) - L +1,如果far[L] >= L的话。 这样,整个问题的解法可以分为以下几个步骤: 1. 预处理left数组,其中left[i]表示以i结尾的最长无重复子数组的左端点。这可以通过滑动窗口和哈希表预处理得到。 2. 预处理far数组,其中far[L]表示从L开始的最远右端点,使得[L, far[L]]中的元素都不重复。这也可以通过双指针预处理得到。 3. 预处理len数组,其中len[i] = i - left[i] +1,并构建ST表以支持区间最大值查询。 4. 对于每个查询[L, R]: a. 计算情况一:找到第一个i >= L,使得left[i] >= L。然后,在区间[i, R]中查询len的最大值,得到max_len1。如果i > R,则max_len1为0。 b. 计算情况二:max_len2 = min(far[L], R) - L +1。如果 far[L] < L,则max_len2为0。 c. 最终的答案是max(max_len1, max_len2) 这样,每个查询的时间复杂度是O(logN)(二分查找) + O(1)(ST表查询) + O(1)(far数组查询)。 现在,问题的关键在于如何预处理left数组、far数组,以及构建ST表。 现在,我需要详细考虑如何预处理这些数组。 首先,预处理left数组: 初始化哈希表last,记录每个元素最后出现的位置。left数组初始化为0。维护一个指针,初始化为0。遍历每个i从0到n-1: left[i] = max(left[i-1], last[a[i]] +1) (假设i>=1) 然后,更新last[a[i]] = i。 注意,当i=0时,left[0] = 0,因为之前没有元素。 这样,得到的left数组是单调不减的吗? 是的。因为每次left[i]的取值是max(left[i-1], ...),所以必然大于等于left[i-1]。所以,left数组是单调不减的。 这很重要,因为这允许我们使用二分查找来找到第一个i >= L,使得left[i] >= L。因为left数组是单调的,所以一旦某个i满足left[i] >= L,那么所有j >=i的left[j] >= L。 因此,对于每个查询L,我们可以用二分查找在区间[L, R]中找到最小的i,使得left[i] >= L。如果这样的i存在且 <= R,那么max_len1是该区间的最大值。否则,max_len1为0。 接下来,预处理far数组: 这个数组的预处理可能需要双指针的方法。维护一个右指针r,初始为0,一个哈希表记录当前窗口中的元素。遍历左指针l从0到n-1: 每次,尽可能地将r右移,直到遇到重复元素。此时,far[l] = r-1。然后,移动左指针到l+1,并从哈希表中移除a[l]。 例如,这个过程类似于求每个左端点l的最远右端点r,使得[l, r)内的元素都不重复。这可能比之前的left数组的预处理更复杂,因为需要维护一个滑动的窗口。 或者,可以维护一个数组far,其中far[l]是最大的r,使得区间[l, r]中的元素都不重复。这可能需要O(n)的时间。 这的预处理方法可以是: 初始化哈希表记录每个元素的出现次数。维护一个右指针r=0。遍历左指针l从0到n-1: 在每次循环中,将r尽可能向右移动,直到无法再移动(即当前元素a[r]是否已经在窗口中存在?或者,直到添加a[r]会导致重复)。 例如,当处理左端点l时,我们需要将窗口初始化为[l, r],然后逐步扩展r,直到发现重复的元素。例如: while r < n,并且当前窗口[l, r)中没有重复的元素: 将a[r]加入哈希表,如果哈希表中a[r]的计数为0,则r增加,否则break。 这样,far[l] = r-1。然后,当l增加时,需要将a[l]从哈希表中移除。 但是,这样的双指针方法的时间复杂度是O(n),因为每个元素最多被访问两次(被l和r指针各一次)。 所以,可以用这种方法预处理far数组。 但是,需要注意的是,当预处理far数组时,每个far[l]的值是最大的r,使得[l, r]中的元素都是唯一的。 这样,对于每个查询的L,max_len2等于min(far[L], R) - L +1。如果 far[L] >= L的话。否则,可能不存在这样的区间,此时max_len2为0? 或者,far[L]的值可能比L大,因为当L自身是一个有效的起始点,比如,当数组中的元素都不重复时,far[L] = n-1。 这样,预处理far数组的正确性需要确保。 现在,假设left数组和far数组都已正确预处理,且ST表构建在len数组上,每个查询的处理步骤如下: 1. 对于情况一,使用二分查找找到第一个i >= L,其中left[i] >= L。这个i的可能范围是[L, R]。如果这样的i不存在,则max_len1为0。否则,max_len1是ST表查询[i, R]的最大len值。 2. 对于情况二,计算max_len2 = min(far[L], R) - L +1。如果 far[L] < L,则说明从L开始的窗口无法扩展到任何元素,所以max_len2为0。否则,取该值和R的最小值后的长度。 然后,取max(max_len1, max_len2)作为答案。 现在,我需要验证这种方法是否正确。 例如,样例输入: 9 2 2 5 4 1 2 3 6 2 4 查询0 8: 各个元素的left数组: i=0: a[i]=2。last没有记录,left[0] = 0. i=1: a[i]=5。last没有,left[1] = max(left[0]=0, ... )=0. 因为last[5] =1。没有冲突。 i=2: a[i]=4。同样,left[2]=0. i=3: a[i]=1。left[3]=0. i=4: a[i]=2。之前出现的位置是0。所以 left[4] = max(left[3]=0, 0+1=1) → 1. i=5: a[i]=3。left[5] = max(1, ... ),因为3未出现过,所以 left[5]=1. i=6: a[i]=6。left[6]=1. i=7: a[i]=2。之前出现在0和4。最近的last[2]=4。所以 left[7] = max(left[6]=1, 4+1=5). →5. i=8: a[i]=4. 之前出现在2的位置。last[4]=2 → left[8] = max(5, 2+1=3) →5. 所以,left数组是:[0,0,0,0,1,1,1,5,5]. len数组是:每个i - left[i]+1 → [1, 2,3,4,4,5,6,3,4]. 现在,对于查询0 8: 情况一:找第一个i >=0,left[i] >=0。因为left数组从0开始,所以i=0。此时,在i=0到8的区间中的len数组的最大值。比如,最大值是6(i=6时len是6)。 情况二:far[0]应该是从0开始的最长无重复子数组的右端点。原数组是:2,5,4,1,2,3,6,2,4. 从0开始的最长无重复子数组是0到3(元素2,5,4,1),因为下一个元素是2,重复了。所以 far[0] =3。此时,R=8。所以min(3,8)=3 →长度是3-0+1=4. 所以情况二的值是4。 但是样例的输出是6,所以此时max(6,4)=6,正确。 另一个样例查询是2-6: L=2, R=6. 原数组是4,1,2,3,6. 情况一:找到第一个i>=2,left[i] >=2. 查看left数组: i=2的left[i]=0 <2. i=3:0 <2. i=4:1 <2. i=5:1 <2. i=6:1 <2. i=7:5 >=2,但R=6。所以,i从2到6,left[i]都 <2。所以情况一的max_len1=0. 情况二:far[2]是多少?从2出发的最长子数组。元素是4,1,2,3,6,在位置2到6的数组元素是4,1,2,3,6。没有重复,所以far[2]应该是6. min(6,6)=6。长度是6-2+1=5。这符合样例的输出5。所以,此时max(0,5)=5,正确。 所以,这种方法是正确的。 接下来,需要确定如何预处理far数组。 预处理far数组的正确性对结果至关重要。例如,对于数组中的每个左端点L,找到最远的R,使得区间[L, R]中的元素都不重复。 预处理far数组的方法是双指针法: 初始化一个哈希表,记录当前窗口中的元素的出现次数。维护两个指针,左指针l和右指针r,初始都为0。遍历每个l,从0到n-1: 在每次循环中,移动r尽可能向右,直到遇到重复元素。此时,far[l] = r-1. 然后,将a[l]从哈希表中移除,如果出现次数减到0的话? 或者,更准确的步骤: 初始化哈希表cnt,记录每个元素出现的次数。初始时,所有元素次数为0。 r初始为0。 for l in 0..n-1: while r <n and cnt[a[r]] ==0: cnt[a[r]] +=1 r +=1 far[l] = r-1 cnt[a[l]] -=1 这样,这样预处理的话,对于每个l,窗口是[l, far[l]],即从l到r-1的区间,其中r是第一个导致重复的元素的位置。所以,该区间的元素都是唯一的。 例如,样例中的数组: 处理l=0时: r=0,a[0]=2,cnt[2]是0,所以加入,r=1. 此时,检查a[1]=5,加入,r=2. a[2]=4,加入,r=3. a[3]=1,加入,r=4. a[4]=2,此时cnt[2]=1(因为之前已经加入了a[0]=2,并且此时l=0还没有被处理,所以cnt[2]是1?或者需要重新分析流程? 抱歉,可能我在这里需要更仔细地模拟这个过程。 例如,当处理l=0时: 初始时,cnt中的所有元素的计数都为0。r=0. 进入循环: r=0,a[r]=2,cnt[2]==0,所以cnt[2] +=1 →cnt[2]=1。r变为1. 继续循环: r=1,a[r]=5,cnt[5]==0 →cnt[5]=1,r=2. r=2,a[r]=4 →cnt[4]=0 →加入,r=3. r=3,a[r]=1 →加入,r=4. r=4,a[r]=2 →此时cnt[2]是1,所以不进入循环。循环终止。 所以,far[0] = r-1 =4-1=3。然后,cnt[a[0]]即cnt[2] -=1 →变为0. 此时,far[0]=3,对应的区间是0~3,元素是2,5,4,1。 处理l=1时: 此时,cnt的计数是:2:0,5:1,4:1,1:1. 需要将r从4开始。 循环中,检查r=4,a[r]=2 →cnt[2]是0 →可以加入。cnt[2]=1,r=5. 检查r=5,a[5]=3 →加入,r=6. r=6,a[6]=6 →加入,r=7. r=7,a[7]=2 →此时cnt[2]是1 →无法加入。循环停止。所以,far[1] =7-1=6. 然后,cnt[a[1]]即5的计数减1 →5的计数变为0. 所以,far[1]是6,对应的区间是1~6的元素5,4,1,2,3,6. 这样,当查询L=2时,far[L]=far[2]等于多少? 处理l=2时: 此时,cnt中的计数:5:0(之前处理l=1时被移除了),4:1(被l=0处理时增加,之后没有被移除?或者需要重新看流程。 可能我的模拟有误,需要更仔细的步骤。 可能,正确的far数组预处理需要维护一个窗口[l, r),其中所有元素都是唯一的。初始时,窗口是[0,0),即空。 然后,对于每个l,我们扩展r到最远的位置,使得窗口中的元素都不重复。然后,记录r-1作为far[l]。然后将l右移,并移除a[l]的计数。 例如,正确的预处理方法: 初始化哈希表cnt,为空。 r=0. for l from 0 to n-1: while r <n and cnt.get(a[r], 0) ==0: cnt[a[r]] = cnt.get(a[r], 0) +1 r +=1 far[l] = r-1 cnt[a[l]] -=1 if cnt[a[l]] ==0: del cnt[a[l]] 这样,对于每个l,窗口[l, far[l]]中的元素都是唯一的。而当处理下一个l+1时,窗口从l+1开始,r可能从之前的位置继续扩展。 例如,在样例中,处理l=0时,r会增加到4(当处理到a[4]=2时,此时cnt[2]=1,所以停止。far[0]=3(r-1=4-1=3)。然后,从窗口中移除a[0]=2,cnt[2]变为0,被删除。 处理l=1时,此时窗口是[1,4)。此时,检查r=4(a[4]=2,cnt中此时没有2的存在),所以可以加入。r增加到5。此时,窗口是[1,5)。继续检查r=5(a[5]=3)加入,r=6。a[6]=6,加入,r=7。a[7]=2,此时cnt中没有2,所以可以加入。r=8。a[8]=4,此时,cnt中是否有4?假设在之前的步骤中,当l=0时,a[2]=4被加入,但l=0的处理结束后,当处理l=1时,此时是否还保留? 这可能复杂,需要详细模拟。 例如,处理l=0时,r增加到4。far[0] =3。此时,移除a[0]=2,cnt[2]减到0,被删除。此时,cnt中的元素是5、4、1,各出现一次。 处理l=1时,此时,窗口的左端是1。此时,我们需要将r从4开始: a[4]是2,cnt中没有2,所以可以加入。此时,cnt[2]=1。r变为5. 检查a[5]是3,可以加入,r=6. 检查a[6]是6,可以加入,r=7. 检查a[7]是2,此时,cnt中已经有2吗?是的,因为之前加入了a[4]=2。所以,此时无法加入。所以,far[1] =7-1=6。窗口是1到6。然后,移除a[1]=5,cnt[5]减到0,被删除。此时,cnt中的元素是4、1、2、3、6,各出现一次。 处理l=2时,此时窗口左端是2。此时,需要检查r=7: a[7]是2,cnt中此时是否有2?是的,因为之前处理l=1时,a[4]被加入。所以,此时不能加入a[7]。所以,far[2] =6。然后,移除a[2]=4,cnt[4]减到0,被删除。此时,cnt中的元素是1、2、3、6. 处理l=3时,左端是3。此时,r=7: a[7]是2,cnt中没有2吗?因为此时cnt中可能已经删除了?或者要看之前的步骤。这需要更细致的模拟,可能比较复杂。 无论如何,对于样例中的查询L=2,far[2]的值应该为6。因为从位置2开始,元素是4、1、2、3、6。这五个元素都是唯一的。当处理到位置7时,元素是2,此时已经存在于窗口中(在位置4),所以无法扩展。所以,far[2] =6(因为r=7时无法加入,所以far[2]=7-1=6)。 此时,查询L=2,R=6时,far[L]=6,min(6,6)=6。所以,长度是6-2+1=5,符合样例输出。 这表明预处理far数组是正确的。 综上,该算法的步骤是正确的。接下来,需要编写代码实现这一思路。 现在,关于ST表的构建,我们需要预处理len数组的最大值。对于每个i,len[i] =i - left[i]+1。然后,构建一个ST表,支持区间最大值查询。这可以通过标准的ST表实现来完成。 最后,对于每个查询: max_len1的计算方法是: 在left数组中,使用二分查找找到第一个i >= L,且left[i] >= L。这可以通过在区间[L, R]中进行二分查找。因为left数组是单调不减的,所以可以使用lower_bound来找第一个大于等于L的left[i]。 例如,在C++中,可以用lower_bound函数,但需要传入合适的比较条件。或者,自己实现二分查找。 找到该i之后,如果i > R,那么max_len1为0。否则,查询ST表中i到R的最大len值。 max_len2的计算是: 如果 far[L] >= L,那么取min(far[L], R) - L +1。否则,取0。 然后,答案是max(max_len1, max_len2)。 综上,该算法的步骤是正确的,并且可以处理大规模的数据,因为预处理时间复杂度为O(n),每个查询的时间复杂度为O(log n)(二分查找) + O(1)(ST表查询)。 现在,编写代码: 首先预处理left数组: int n, m; cin >>n >>m; vector<int> a(n); for (int i=0; i<n; i++) cin >>a[i]; 预处理left数组: vector<int> left(n); unordered_map<int, int> last; left[0] =0; last[a[0]] =0; for (int i=1; i<n; i++){ left[i] = left[i-1]; if (last.count(a[i])){ left[i] = max(left[i], last[a[i]] +1); } last[a[i]] =i; } 预处理far数组: vector<int> far(n); unordered_map<int, int> cnt; int r=0; for (int l=0; l<n; l++){ while (r <n && cnt[a[r]] ==0){ cnt[a[r]]++; r++; } far[l] = r-1; cnt[a[l]]--; if (cnt[a[l]] ==0){ cnt.erase(a[l]); } } 预处理ST表: struct ST{ vector<vector<int>> st; int k; ST(vector<int>& arr){ int n = arr.size(); k = log2(n) +1; st.resize(n, vector<int>(k)); for (int i=0; i<n; i++){ st[i][0] = arr[i]; } for (int j=1; j<k; j++){ for (int i=0; i + (1<<j) <=n; i++){ st[i][j] = max(st[i][j-1], st[i + (1<<(j-1))][j-1]); } } } int query(int l, int r){ if (l >r) return 0; int len = r-l+1; int j = log2(len); return max(st[l][j], st[r - (1<<j)+1][j]); } }; 然后,预处理len数组,并构建ST表: vector<int> len(n); for (int i=0; i<n; i++){ len[i] =i - left[i] +1; } ST st(len); 处理每个查询: while (m--){ int L, R; cin >>L >>R; // 找第一个i >=L,且left[i] >=L. int low = L, high = R; int i = R+1; while (low <= high){ int mid = (low + high)/2; if (left[mid] >=L){ i = mid; high = mid-1; } else { low = mid+1; } } int max_len1 =0; if (i <=R){ max_len1 = st.query(i, R); } int max_len2 =0; if (far[L] >=L){ int right = min(far[L], R); max_len2 = right -L +1; } cout << max(max_len1, max_len2) << endl; } 这样,代码的大体结构就完成了。但需要注意,ST表的实现是否正确,以及处理边界条件。 例如,当L=0,R=0时,如何处理? 此外,预处理far数组时,当所有元素都是唯一的,r可能等于n,此时far[l] =n-1. 另外,在预处理far数组时,必须保证r不会超过n的范围。 测试样例中的情况是否被正确处理? 对于样例输入中的第一个查询0-8: max_len1的情况: 通过二分查找找到第一个i >=0,且left[i] >=0。由于left数组是[0,0,0,0,1,1,1,5,5],所以i=0。此时查询ST表中的0到8的最大len值。len数组是[1,2,3,4,4,5,6,3,4]。最大值是6,在i=6的位置。max_len1=6. max_len2: far[0]=3 →3 >=0 →取min(3,8)=3 →3-0+1=4. 所以max(6,4)=6. 第二个查询2-6: 二分查找在区间2-6找第一个i >=2,且left[i] >=2. left数组中的i=2到i=6的left值分别为0,0,1,1,1。这些都小于2。所以i=R+1=7,超出范围,max_len1=0. max_len2: far[2]的值,假设far数组预处理正确的话是6。min(6,6)=6 →6-2+1=5. 所以答案是5. 这说明代码是正确的。 但是,在预处理far数组时,可能存在错误。例如,当处理l=0时,在样例中,far[l]=3。那么,在代码中,当处理l=0时,r会移动到4的位置。此时,far[0]是3。然后,当处理l=1时,r的初始位置是4。这时,检查a[4]=2是否在当前的cnt中。因为当处理l=0时,cnt移除了a[0]=2,所以此时cnt中没有2,所以a[4]=2可以加入。 这可能正确。所以,far数组的预处理是正确的。 综上,该算法是正确的,并且可以通过所有测试用例。</think>```cpp #include <bits/stdc++.h> using namespace std; struct ST { vector<vector<int>> st; int k; ST(vector<int>& arr) { int n = arr.size(); k = log2(n) + 1; st.resize(n, vector<int>(k)); for (int i = 0; i < n; ++i) { st[i][0] = arr[i]; } for (int j = 1; j < k; ++j) { for (int i = 0; i + (1 << j) <= n; ++i) { st[i][j] = max(st[i][j-1], st[i + (1 << (j-1))][j-1]); } } } int query(int l, int r) { if (l > r) return 0; int j = log2(r - l + 1); return max(st[l][j], st[r - (1 << j) + 1][j]); } }; int main() { ios::sync_with_stdio(false); cin.tie(0); int n, m; cin >> n >> m; vector<int> a(n); for (int i = 0; i < n; ++i) { cin >> a[i]; } vector<int> left(n); unordered_map<int, int> last; left[0] = 0; last[a[0]] = 0; for (int i = 1; i < n; ++i) { left[i] = left[i - 1]; if (last.count(a[i])) { left[i] = max(left[i], last[a[i]] + 1); } last[a[i]] = i; } vector<int> far(n); unordered_map<int, int> cnt; int r = 0; for (int l = 0; l < n; ++l) { while (r < n && cnt[a[r]] == 0) { cnt[a[r]]++; r++; } far[l] = r - 1; cnt[a[l]]--; if (cnt[a[l]] == 0) { cnt.erase(a[l]); } } vector<int> len(n); for (int i = 0; i < n; ++i) { len[i] = i - left[i] + 1; } ST st(len); while (m--) { int L, R; cin >> L >> R; int low = L, high = R; int i = R + 1; while (low <= high) { int mid = (low + high) / 2; if (left[mid] >= L) { i = mid; high = mid - 1; } else { low = mid + 1; } } int max_len1 = 0; if (i <= R) { max_len1 = st.query(i, R); } int max_len2 = 0; if (far[L] >= L) { int right_most = min(far[L], R); max_len2 = right_most - L + 1; } cout << max(max_len1, max_len2) << '\n'; } return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值