浅谈一类第 k 大问题
Introduction to K-th Largest Problems
本文介绍一类第 k 大问题的处理方法。
Luogu P1631 序列合并
Luogu P2048 [NOI2010] 超级钢琴
Luogu P5283 [十二省联考 2019] 异或粽子
CodeForces 241B Friends
基本思想:先找到部分答案,通过这部分答案更新可能的新答案。即使得当前维护的集合内一定有全局最优解,通过全局最优解更新维护的集合并保证全局最优解一定在当前维护的集合中。
Luogu P1631 序列合并
题意:给定两个长为 \(n\) 的不降数列 \(a, b\),在它们中各取一个数可以得到 \(n^2\) 个和,求最小的 \(n\) 个。\(1 \leqslant n \leqslant 10^5, 1 \leqslant a_i, b_i \leqslant 10^9\)。
发现 \(a_1 + b_1\) 一定是最小的,先将 \(a_1 + b_1\) 加入优先队列,每次弹出最小元素 \(a_i + b_j\),然后将下一次可能成为最小值的 \(a_{i + 1}, b_j\),\(a_i + b_{j + 1}\) 和 \(a_{i + 1} + b_{j + 1}\) 入队。于是队列中元素被控制在 \(O(n)\) 级别,时间复杂度 \(O(n \log n)\),可以通过。
Luogu P2048 [NOI2010] 超级钢琴
题意:给定长为 \(n\) 的数列 \(a\),求长度在 \(L\) 到 \(R\) 之间的子串的和的前 \(k\) 大值的和。\(1 \leqslant n, k \leqslant 5 \cdot 10^5, -10^3 \leqslant a_i \leqslant 10^3, 1 \leqslant L \leqslant R \leqslant n\),保证存在满足要求的子串。
处理前缀和数组 \(f\),题目即为求前 \(k\) 大的 \(f_j - f_i\)(这里用 \(i\) 代替了 \(i - 1\),便于处理)之和。
对每个位置 \(i \in [0, n - L]\),求出在 \([i + L, \min\{i + R, n\}]\) 中的 \(j\),使得 \(f_j\) 最大。这样得到的答案中一定包含全局最大的子串。
设状态 \(\{o, t, l, r\}\) 表示子串的起点为 \(o\),终点在 \([l, r]\) 之间选择,选择 \(t\) 为终点时取到最大值。
初始会将 \(\{o, t, o + l, \min\{o + R, n\}\} (o \in [0, n - L])\) 入队。每次提取出一个状态 \(\{o, t, l, r\}\) 后,将(如果合法)\(\{o, t', l, t - 1\}\) 和 \(\{o, t'', t + 1, r\}\) 入队。
于是优先队列中的元素被控制在 \(O(n + k)\) 级别,最大值用 ST 表求,可以通过。
Luogu P5283 [十二省联考 2019] 异或粽子
题意:给定长为 \(n\) 的数列 \(a\),求子串的异或和的前 \(k\) 大值的和。\(1 \leqslant n \leqslant 5 \cdot 10^5, 1 \leqslant k \leqslant 5 \cdot 10^5, 0 \leqslant a_i \leqslant 4294967295\),保证存在满足要求的子串。
处理前缀异或和数组 \(f\),题目即为求前 \(k\) 大的 \(f_j \oplus f_i\)(这里用 \(i\) 代替了 \(i - 1\),便于处理)之和。
对每个位置 \(i \in [0, n - 1]\),求出在 \([i + 1, n]\) 中的 \(j\),使得 \(f_j \oplus f_i\) 最大。这样得到的答案中一定包含全局最大的子串。
设状态 \(\{o, t, l, r\}\) 表示子串的起点为 \(o\),终点在 \([l, r]\) 之间选择,选择 \(t\) 为终点时取到最大值。
初始会将 \(\{o, t, o + 1, n\} (o \in [0, n - 1])\) 入队。每次提取出一个状态 \(\{o, t, l, r\}\) 后,将(如果合法)\(\{o, t', l, t - 1\}\) 和 \(\{o, t'', t + 1, r\}\) 入队。
于是优先队列中的元素被控制在 \(O(n + k)\) 级别,最大异或和用 0-1 Trie 求,可以通过。
CodeForces 241B Friends
本题因为 \(m\) 很大,不能套用前文所述的解法。
换一种思路:考虑把 \(a_i\) 从高位到低位插入 0-1 Trie 之后,二分第 \(m\) 大,通过第 \(m\) 大求答案。
对于二分的一个值 \(x\),枚举每个位置 \(i\),在 0-1 Trie 上找与 \(a_i\) 异或值大于等于 \(x\) 的值的个数。
类比求最大异或和的过程,考虑搜索到第 \(j\) 位。如果 \(x\) 的第 \(j\) 位为 \(1\),为了最终异或值大于等于 \(x\),可能的数一定在与 \(a_i\) 的第 \(j\) 位相异的子树中,递归即可;反之,如果 \(x\) 的第 \(j\) 位为 \(0\),与 \(a_i\) 的第 \(j\) 位相异的子树中的值一定全部满足条件,递归与 \(a_i\) 的第 \(j\) 位相同的子树即可。
于是可以在 \(O(n \log^2 w)\) 的时间内找到第 \(m\) 大的异或值(\(w\) 为值域,后文同),设这个值为 \(k\)。
下一步是求前 \(m\) 大两两异或值的和。
容易想到,类似前文所述,只需要处理被计算的完整的子树与 \(a_i\) 的异或值的和。(即搜索时找到的 \(k\) 的第 \(j\) 位为 \(0\),与 \(a_i\) 的第 \(j\) 位相异的子树)直接对这些子树的根节点打标记,整体遍历一次 0-1 Trie 时容易得到这棵子树内每一位上 \(0\) 和 \(1\) 的数量,答案也就容易统计了。
至多有 \(n \log w\) 个标记,处理每个标记需要枚举 \(\log w\) 位。同时,至多合并 \(O(n \log w)\) 次,单次合并的时间为 \(O(\log w)\)。综上,时间复杂度 \(O(n \log^2 w)\)。
将两部分拼起来就得到了最终做法,时间复杂度 \(O(n \log^2 w)\),可以通过。