G1. 大胜! (简单版)
问题描述
这是问题的简单版本。版本之间的区别在于,在此版本中 。
给定一个包含 个整数的数组 。
你的任务是找到一个子数组 (一个连续的元素序列 ),使得表达式 的值最大化。
这里:
- — 子数组的中位数,即子数组排序后位于位置 的元素,其中 是其长度;
- — 该子数组的最小元素。
示例
例如,考虑数组 并选择子数组 。排序后,它看起来像 。
- ,因为 ,所以排序后子数组中的第三个元素是 4;
- ,因为最小元素是 1。
在这个例子中,。
输入
第一行包含一个整数 — 测试用例的数量。
每个测试用例的第一行包含一个整数 — 数组的长度。
每个测试用例的第二行包含 个整数 — 数组的元素。
保证所有测试用例的 的总和不超过 。
输出
对于每个测试用例,输出一个整数 — 数组所有子数组中 的最大可能值。
题解概述
问题要求我们找到一个子数组 ,使得 最大化。约束条件 在这里至关重要,因为它将数组中的可能值限制在一个很小的范围内(1到100)。
所提供的解法利用了这个小的值范围,通过遍历所有可能的中位数值。
以下是解法的详细分解:
理解策略:遍历中位数 ()
核心思想是固定一个潜在的中位数值,我们称之为 。由于数组的值在1到100之间, 也只能在1到100之间。我们将尝试这100个可能的 值。
对于一个固定的 ,我们的目标是找到一个子数组 ,使得:
- (中位数至少为 )
- 我们想要最大化 。这意味着我们想找到一个中位数至少为 且其最小元素尽可能小的子数组。
固定中位数 的分步解释:
将数组转换为 : 对于一个固定的 ,我们创建一个与 大小相同的新数组 。每个元素 的确定如下:
- 如果 ,则
- 如果 ,则
为什么这样转换? 中位数的定义依赖于大多数元素。一个子数组 的中位数至少为 当且仅当其一半以上的元素 。 如果我们对一个子数组 的 值求和(我们称之为 ),这个和将告诉我们大于等于 的元素数量与小于 的元素数量的对比。
- 每个 对和的贡献是 。
- 每个 对和的贡献是 。 如果和 ,意味着子数组中 的元素多于 的元素。这确保了子数组的中位数至少为 。(具体来说,对于长度为 的子数组,如果 的元素数量为 , 的元素数量为 ,那么 。如果 ,则 。由于 ,这意味着 。这正是中位数 的条件。)
使用前缀和计算子数组和: 我们计算 数组的前缀和:。我们定义 。 一个子数组 的 值之和是 。 因此,条件“”等价于“”,或 。
为潜在中位数寻找“有效”索引: 对于一个固定的 ,我们想知道每个位置 是否可以成为 某个 中位数至少为 的子数组的一部分。如果存在一个包含 的子数组 使得 ,那么这是成立的。 这意味着我们需要检查是否存在一个 使得 。
解法中提出了两个条件:
- :这意味着在 之前存在一个起点 使得 。换句话说,存在一个以 结尾的子数组 ,其中位数 。
- :这意味着在 或之后存在一个终点 使得 。换句话说,存在一个以 开始的子数组 ,其中位数 。
为了高效地检查每个 的这些条件:
- 前缀最小值 (): 计算 。这使我们能快速检查 。
- 后缀最大值 (): 计算 。这使我们能快速检查 。
如果 或 (注意:索引可能需要根据0-索引与1-索引进行仔细调整,但概念是成立的),那么位置 就可以是中位数 的子数组的一部分。
最大化 : 对于每个满足步骤3中任一条件的位置 (意味着 可以是中位数至少为 的子数组的一部分),我们将 视为该子数组的一个潜在最小元素。 如果我们将 选为最小元素,并且我们知道存在一个包含 且中位数至少为 的子数组,那么我们可以实现一个值为 的差。 我们想要最大化 。由于我们固定了 ,要最大化 ,我们应该尽量减小 。对于一个包含 且中位数 的子数组,可能的最小 就是 本身,如果我们选择一个 确实是最小值的子数组 。
解法中说:“如果任一不等式成立,索引 就可以位于某个中位数 的子数组中。我们可以自由地将该 作为所选子数组的最小值,得到值 。因此,对于那个 我们可以实现差值 。在所有满足测试的索引中,保留最大的差值。” 这是一个关键的简化。这意味着我们不需要为每个 显式地找到 最优 子数组。如果 可以是 任何 有效的中位数 的子数组的一部分,我们就 假设 我们可以形成一个 是最小值且中位数 的子数组。这是一种贪心方法。
所以,对于一个固定的 ,我们遍历所有 从 到 。如果 是“有效的”(可以是中位数 的子数组的一部分),我们就用 更新我们目前找到的最大差值。
整体算法:
- 初始化
max_difference = -infinity(或一个非常小的数)。 - 遍历
m从 1 到 100: a. 创建数组b,其中如果a[j] >= m则b[j] = +1,否则b[j] = -1。 b. 计算b的前缀和pref。(pref[0] = 0,pref[k] = pref[k-1] + b[k]) c. 计算pref的前缀最小值prefmn。(prefmn[j] = min(prefmn[j-1], pref[j])) d. 计算pref的后缀最大值suffmx。(suffmx[j] = max(suffmx[j+1], pref[j])) e. 遍历j从 1 到n: i. 检查prefmn[j-1] < pref[j](以 结尾且中位数 的子数组)。 ii. 检查pref[j-1] < suffmx[j](以 开始且中位数 的子数组)。 iii. 如果任一条件成立,更新max_difference = max(max_difference, m - a[j])。 - 输出
max_difference。
复杂度:
时间复杂度:
- 外层循环运行100次(对于 到 )。
- 循环内部:
- 构建
b: - 计算
pref: - 计算
prefmn: - 计算
suffmx: - 扫描
j并更新max_difference:
- 构建
- 总时间复杂度:。
- 鉴于 ,,这在4秒的时间限制内是完全可以接受的。
空间复杂度:
- 我们需要
a,b,pref,prefmn,suffmx的数组,大小均为 。 - 总空间复杂度:。
- 鉴于 ,这在256MB的内存限制内是可行的。
- 我们需要
示例演练(简化):
设
我们选择 。
为 构建 :
前缀和 :() (0-indexed 到 )
前缀最小值 :
后缀最大值 :(假设 是 ) (从右到左: , , , 等)
遍历 并检查有效性:
- 对于 : 。 是 FALSE。。 是 FALSE。 对 无效。
- 对于 : 。 是 TRUE。 有效。。
- 对于 : 。 是 FALSE。。 是 FALSE。 对 无效。
- 对于 : 。 是 TRUE。 有效。。
- 对于 : 。 是 FALSE。。 是 FALSE。 对 无效。
- 对于 : 。 是 FALSE。。 是 FALSE。 对 无效。
对于 ,找到的最大差值为 。
如果我们尝试 :
我们检查 : 。 是 FALSE。 。 是 TRUE。 有效。 。
该解法的逻辑是合理的,并利用了数组中值的范围小的特点,高效地检查了所有可能的中位数值。