1. 题目背景与核心问题解析
这道来自Codeforces竞赛平台编号1710E的题目"Two Arrays",乍看之下是个典型的双序列操作问题。但深入分析后会发现,它巧妙融合了贪心策略、二分查找和前缀和优化等多个算法考点。题目给定两个长度分别为n和m的数组a和b,要求通过特定操作使两个数组的中位数尽可能大。
中位数这个统计量在算法题中往往意味着需要处理有序性。对于偶数长度数组,题目采用常见的下中位数定义(即第k/2小的元素)。而操作规则允许我们在两个数组间交换元素,这提示我们需要找到某种最优的分配策略。
2. 关键算法思路拆解
2.1 问题转化与数学模型建立
首先我们需要明确目标:通过交换操作最大化合并数组的中位数。设最终中位数为target,那么根据定义,合并数组中至少要有⌈(n+m)/2⌉个元素≤target。这个观察让我们可以将问题转化为:
寻找最大的target,使得存在一种交换方式,使得a和b数组中≤target的元素总数≥⌈(n+m)/2⌉
这自然引导我们想到二分答案的思路。因为当target越大时,满足条件的难度越大,具有单调性。
2.2 双指针与贪心策略
对于每个二分过程中的target值,我们需要高效验证是否存在满足条件的交换方案。这里可以采用双指针技术:
- 对数组a和b分别排序(预处理)
- 使用指针i从a的头部开始,j从b的尾部开始
- 统计a[i] ≤ target且b[j] > target的元素对
- 这些对可以通过交换来增加≤target的元素数量
这个策略的核心在于:每次交换都能"净增加"一个≤target的元素(因为用a中的小元素替换b中的大元素)。
3. 算法实现细节与优化
3.1 预处理与排序
python复制a.sort()
b.sort()
n, m = len(a), len(b)
total = n + m
half = (total + 1) // 2
排序是后续所有操作的基础,时间复杂度O(n log n + m log m)。注意half的计算方式处理了奇偶长度的情况。
3.2 二分查找框架
python复制left, right = min(a[0], b[0]), max(a[-1], b[-1])
answer = left
while left <= right:
mid = (left + right) // 2
if check(mid):
answer = mid
left = mid + 1
else:
right = mid - 1
标准的二分查找结构,check函数用于验证当前mid值是否可以作为候选答案。
3.3 验证函数的实现
python复制def check(target):
# 计算不交换时≤target的数量
cnt = bisect.bisect_right(a, target) + bisect.bisect_right(b, target)
if cnt >= half:
return True
# 计算通过交换可以增加的数量
i, j = 0, m - 1
extra = 0
while i < n and j >= 0:
if a[i] + b[j] > 2 * target:
j -= 1
else:
extra += (j + 1)
i += 1
return (cnt + extra) >= half
这个验证函数是算法的核心,它首先统计不交换时的满足条件元素数量,然后计算通过交换可以额外增加的数量。
4. 复杂度分析与边界处理
4.1 时间复杂度
- 排序阶段:O(n log n + m log m)
- 二分阶段:O(log(max_val - min_val))
- 每次check操作:O(n + m)
总复杂度为O((n+m) log(max_val - min_val)),在题目约束下完全可行。
4.2 边界情况处理
需要特别注意的特殊情况包括:
- 两个数组长度都为1
- 所有元素相同的情况
- 一个数组全部元素大于另一个数组的情况
这些边界情况可以通过在check函数中添加特殊判断来处理。
5. 完整代码实现
python复制import bisect
def solve():
import sys
input = sys.stdin.read
data = input().split()
ptr = 0
n, m = int(data[ptr]), int(data[ptr+1])
ptr +=2
a = list(map(int, data[ptr:ptr+n]))
ptr +=n
b = list(map(int, data[ptr:ptr+m]))
a.sort()
b.sort()
total = n + m
half = (total + 1) // 2
def check(target):
cnt = bisect.bisect_right(a, target) + bisect.bisect_right(b, target)
if cnt >= half:
return True
i, j = 0, m - 1
extra = 0
while i < n and j >= 0:
if a[i] + b[j] > 2 * target:
j -= 1
else:
extra += (j + 1)
i += 1
return (cnt + extra) >= half
left, right = min(a[0], b[0]), max(a[-1], b[-1])
answer = left
while left <= right:
mid = (left + right) // 2
if check(mid):
answer = mid
left = mid + 1
else:
right = mid - 1
print(answer)
solve()
6. 算法优化与变种思考
6.1 可能的优化方向
- 提前终止:在check函数中,当cnt + extra ≥ half时可以立即返回
- 预处理前缀和:对于某些特定数据分布可以加速统计
- 并行处理:对于超大数组可以考虑分块处理
6.2 题目变种与扩展
- 如果操作改为只能交换相邻元素,问题将变为更复杂的动态规划问题
- 如果要求两个数组的中位数分别最大化,则需要不同的策略
- 如果操作有代价限制,则可能需要结合费用流等高级算法
7. 实际竞赛中的应用技巧
在编程竞赛中遇到此类问题时,可以快速执行以下步骤:
- 明确题目要求的中位数定义(上/下中位数)
- 观察操作的性质,分析其对统计量的影响
- 尝试将最优化问题转化为判定性问题(二分答案)
- 设计高效的验证函数(通常需要贪心或双指针)
- 特别注意边界条件和特殊测试用例
这道题的典型解决时间应控制在30-45分钟内,包括思考、编码和测试。在竞赛中,建议先写好二分框架和check函数原型,再逐步完善细节。
