从CCPC河南省赛H题‘随机栈’出发,手把手教你用C++ STL priority_queue和map实现贪心与模运算
从随机栈问题到STL实战贪心策略与模运算的竞赛技巧在算法竞赛中数据结构的选择和数学技巧的应用往往是解题的关键。本文将以CCPC河南省赛H题随机栈为例深入探讨如何利用C STL中的priority_queue和map实现高效的贪心策略并处理模运算下的概率计算问题。1. 问题背景与核心思路随机栈问题描述了一个包含2n次操作的场景每次操作要么将一个数字放入集合要么从当前集合中取出一个数字。最终要求取出的数字序列严格递增的概率结果需要对998244353取模。这个问题的核心在于贪心策略每次取出当前集合中最小的数字确保序列递增概率计算每次操作时选择最小数字的概率等于当前集合中最小数字的个数除以集合大小模运算处理由于概率涉及分数需要使用模逆元进行计算const int mod 998244353; int quick_mi(int a, int b) { int ans 1; while(b) { if(b % 2) ans ans * a % mod; a a * a % mod; b 1; } return ans; }2. 数据结构的选择与实现2.1 使用优先队列维护最小值priority_queue是C STL中实现堆数据结构的容器适配器。在本题中我们需要一个小根堆来快速获取当前集合中的最小值priority_queueint, vectorint, greaterint min_heap;每次插入操作直接将数字压入堆中min_heap.push(x);2.2 使用map维护数字出现次数为了统计每个数字在当前集合中的出现次数我们使用map来维护unordered_mapint, int count_map;当插入数字x时count_map[x];当取出数字时我们需要知道当前最小数字的出现次数int min_val min_heap.top(); int cnt count_map[min_val];3. 贪心策略的详细实现贪心算法的正确性基于以下观察如果当前取出的数字小于之前取出的最大值则无法形成递增序列概率为0否则每次必须取出当前最小的数字才能保证后续可能形成递增序列实现步骤初始化最大值为0分子和分母的累积变量遍历所有操作如果是插入操作更新堆和计数如果是取出操作检查当前最小值是否大于之前最大值更新概率的分子和分母从堆中移除该数字int max_so_far 0; long long numerator 1, denominator 1; for(int i 0; i 2*n; i) { if(op[i] 0) { // 插入操作 min_heap.push(op[i]); count_map[op[i]]; } else { // 取出操作 int current_min min_heap.top(); if(current_min max_so_far) { cout 0 endl; return; } max_so_far max(max_so_far, current_min); numerator numerator * count_map[current_min] % mod; denominator denominator * min_heap.size() % mod; count_map[current_min]--; min_heap.pop(); } }4. 模运算与概率计算在模数运算下分数p/q的计算需要转换为p×q⁻¹ mod MOD。这里q⁻¹是q的模逆元可以使用费马小定理计算注意模逆元仅在模数为质数且与被模数互质时存在计算最终结果的代码long long result numerator * quick_mi(denominator, mod-2) % mod; cout result endl;5. 常见错误与优化技巧5.1 常见错误分析贪心策略错误尝试其他取数策略而非总是取最小值概率计算顺序错误在模运算下必须先计算分子分母的积最后统一求逆元堆与计数不同步取出数字后忘记更新计数或堆5.2 性能优化提前终止一旦发现当前最小值小于之前最大值立即返回0批量处理可以累积计算分子分母减少模运算次数内存预分配预先分配足够空间给堆和map避免动态扩容开销// 优化预分配内存 min_heap.reserve(n); count_map.reserve(n);6. 扩展应用与类似问题这种结合贪心策略和模运算的技巧在竞赛中非常常见类似的问题包括概率期望问题需要计算大量概率的乘积并对大质数取模贪心选择问题需要动态维护当前最优选择数据结构综合应用同时需要堆和哈希表的功能一个类似的经典问题是TopK问题同样可以使用堆来解决但不需要模运算部分。7. 实战演练与代码模板下面给出完整的解题代码模板包含所有关键部分#include bits/stdc.h using namespace std; const int MOD 998244353; long long quick_pow(long long a, long long b) { long long res 1; while(b) { if(b 1) res res * a % MOD; a a * a % MOD; b 1; } return res; } void solve() { int n; cin n; vectorint operations(2*n); for(int i 0; i 2*n; i) { cin operations[i]; } priority_queueint, vectorint, greaterint min_heap; unordered_mapint, int count_map; int max_val 0; long long p 1, q 1; for(int op : operations) { if(op ! -1) { min_heap.push(op); count_map[op]; } else { if(min_heap.empty()) { cout 0 \n; return; } int current min_heap.top(); if(current max_val) { cout 0 \n; return; } max_val current; p p * count_map[current] % MOD; q q * min_heap.size() % MOD; count_map[current]--; if(count_map[current] 0) { count_map.erase(current); } min_heap.pop(); } } long long inv_q quick_pow(q, MOD-2); long long result p * inv_q % MOD; cout result \n; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); solve(); return 0; }8. 进阶思考与变种问题理解了基础解法后可以考虑以下变种问题操作序列不确定如果操作序列是动态生成的如何在线处理多条件约束除了严格递增还需要满足其他条件如奇偶性更大数据范围当n达到1e6时如何进一步优化对于第一个变种可以考虑使用树状数组或线段树来维护动态集合的统计信息。对于第三个变种可能需要优化哈希表的实现或使用更高效的数据结构。在实际比赛中理解问题本质并选择合适的数据结构往往比编码本身更重要。这道题很好地展示了如何将数学知识与数据结构结合来解决复杂问题。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2562486.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!