Codeforces 1837F Editorial for Two
题目来自 Codeforces Educational Round #149 (Div.2) F。link
This article provides a feasible solution to a certain competitive programming problem. If you have better solutions, feel free to share with me!
Problem Description
给出长度为 \(n\) 的数列 \(\{a_1,a_2,...,a_n\}\) \((1\leq a_i\leq10^9)\),选出一个长度为 \(k\) 的子序列 \((1\leq k\leq n\leq 3\cdot 10^5)\),并将该子序列分为左右两部分。找到合适的划分方案,使得 \(\{\max(\)左侧元素之和,右侧元素之和\()\}\) 最小,并求出该最小值。数据组数 \(1\leq t\leq 10^4\),保证 \(\sum n\leq 3\times 10^5\)。
Tutorial
首先最小化最大值一眼二分答案。考虑当前二分的值为 \(x\)。问题转化为,在长度为 \(n\) 的数列中,选出两个长度之和为 \(k\) 的子序列,使得这两个子序列的元素之和均 \(\leq x\)。并且,这两个子序列不重合:第一个子序列的末尾元素一定在第二个子序列的起始元素之前。
考虑维护前缀数组 \(pre_i\) 与后缀数组 \(suf_i\),标记在前缀 \(a_{1...i}\) 或后缀 \(a_{i...n}\) 中,最多能够选择多少个元素,使得这些元素之和 \(\leq x\)。若我们能够成功计算出 \(pre_i\) 与 \(suf_i\),那么只要使得 \(pre_i+suf_{i+1}\geq k\) 的 \(i\) 存在,就能选出两个不重合,且元素之和 \(\leq x\) 的两个子序列。
关于 \(pre_i\) 与 \(suf_i\) 的计算,这里采取一个简单但巧妙的贪心算法:以 \(pre_i\) 为例,维护一个大根堆 (或优先队列),堆内所有元素的和 \(sum\) 与堆的大小 \(size\)。对于新元素 \(a_i\),先将 \(a_i\) 加入堆中,并更新 \(sum\), \(size\)。若 \(sum\leq x\),\(pre_i=size\);如果 \(sum>x\),则持续弹出堆顶元素直至 \(sum\leq x\),再设置 \(pre_i=size\)。
有人把这种算法称为反悔堆:弹出堆顶元素其实就是“反悔”之前做出的选择,贪心更新最优解的过程。
Code Implementation
1 |
|