【算法题解 | 基德的神秘冒险】:当组合数学遇上二分查找与前缀和

张开发
2026/5/5 5:41:32 15 分钟阅读
【算法题解 | 基德的神秘冒险】:当组合数学遇上二分查找与前缀和
在算法刷题的过程中我们经常会遇到一些看似需要暴力枚举但实际上会被庞大数据量如3×1053 \times 10^53×105直接“教做人”的题目。今天我们要拆解的这道【基德的神秘冒险】就是一道披着“全排列/组合”外衣实则考察数据压缩与二分查找的经典好题。本文将详细复盘这道题的解题思路带你一步步从“内存溢出”的暴力想法蜕变为时间复杂度O(Nlog⁡N)O(N \log N)O(NlogN)的满分优雅代码。题目核心要求一句话题意给定一个长度为NNN的数组AAA从里面任选 3 个不同的数组成一个“三重石”组合这个组合的“力量值”是这 3 个数中的最小值。现在有QQQ次查询每次查询要求输出在所有可能的组合中力量值排在第KKK小的那个数值是多少。数据范围警告3≤N≤3×1053 \le N \le 3 \times 10^53≤N≤3×1051≤Q≤3×1051 \le Q \le 3 \times 10^51≤Q≤3×105K≤(N3)K \le \binom{N}{3}K≤(3N​)思路破局为什么不能暴力踩坑思路暴力枚举 数组存储第一直觉往往是我把所有的组合找出来计算每个组合的最小值存入一个大数组ans中排序后直接通过ans[K]访问不就好了致命问题内存与时间双重爆炸MLE TLE当N3×105N 3 \times 10^5N3×105时总组合数为(N3)≈4.5×1015\binom{N}{3} \approx 4.5 \times 10^{15}(3N​)≈4.5×1015。且不说计算需要多久单单是把这些数字存进数组都需要几千 TB 的内存因此“展开”数组是绝对行不通的我们必须寻找规律。满分思路排序 组合数学 前缀和 二分既然不能穷举所有的组合我们能不能统计每个数字作为“最小值”出现的次数呢第一步排序让“最小值”一目了然我们将数组arr从小到大排序。排序后有一个巨大的好处对于当前数字arr[i]它右边的所有数字都比它大或等于它。因此如果我们要让arr[i]成为一个“三重石”的最小值另外两块石头必须从它右侧的元素中挑选。第二步组合数学计算贡献度假设数组长度为NNN当前元素下标为iii下标从 0 开始。它右边还有mN−1−im N - 1 - imN−1−i个元素。从这mmm个元素中任意挑选 2 个方案数就是组合数公式C(m,2)m×(m−1)2C(m, 2) \frac{m \times (m - 1)}{2}C(m,2)2m×(m−1)​这就意味着在所有的三重石组合中有C(m,2)C(m, 2)C(m,2)个组合是以arr[i]作为最小值的第三步前缀和构建查询梯子知道了每个元素作为最小值的次数我们怎么回答“第KKK小是谁”这个问题呢我们可以构建一个前缀和数组prepre[i]表示以arr[0]到arr[i]为最小值的组合数总和。因为数组是按升序排列的前缀和也是单调递增的这简直就是为二分查找量身定制的第四步二分查找对于每次查询的KKK我们只需要在单调递增的pre数组中找到第一个大于等于KKK的位置idx。对应的arr[idx]就是我们要找的第KKK小的力量数值核心代码实现 (Python)在应对3×1053 \times 10^53×105级别的数据量时除了算法逻辑正确工程上的 IO 优化也至关重要。使用常规的input()会导致超时这里我们采用sys.stdin.read进行极致优化。importosimportsysfrombisectimportbisect_left#48minn,qmap(int,input().split())arrlist(map(int,input().split()))arr.sort()pre[]cur_sum0foriinrange(1,n-1):mn-i cur_sum(m*(m-1))//2pre.append(cur_sum)ans[]for_inrange(q):kint(input())idxbisect_left(pre,k)ans.append(arr[idx])print(\n.join(map(str,ans)))复杂度分析时间复杂度排序O(Nlog⁡N)O(N \log N)O(NlogN)构建前缀和O(N)O(N)O(N)二分查询每次查询O(log⁡N)O(\log N)O(logN)QQQ次查询共O(Qlog⁡N)O(Q \log N)O(QlogN)。总时间复杂度O((NQ)log⁡N)O((N Q) \log N)O((NQ)logN)。在 Python 10s 的限制下面对3×1053 \times 10^53×105的数据规模运行如丝般顺滑。空间复杂度存储数组和前缀和需要O(N)O(N)O(N)的空间答案数组需要O(Q)O(Q)O(Q)的空间完全满足题目256MB256\text{MB}256MB的内存限制。总结与反思这道题是一道非常典型的**“计数类二分”**题型。它告诉我们一个深刻的道理当题目让你求“第KKK个组合/排列”的值且KKK的范围极其巨大时永远不要试图去生成具体的组合。正确的做法是通过排序确定单调性利用组合数学计算分布最后用前缀和 二分查找进行定位。如果你觉得这篇文章对你有帮助欢迎点赞收藏如果有更好的思路也欢迎在评论区一起讨论~

更多文章