当前位置: 首页 > news >正文

从CCPC河南省赛H题‘随机栈’出发,手把手教你用C++ STL priority_queue和map实现贪心与模运算

从随机栈问题到STL实战:贪心策略与模运算的竞赛技巧

在算法竞赛中,数据结构的选择和数学技巧的应用往往是解题的关键。本文将以CCPC河南省赛H题"随机栈"为例,深入探讨如何利用C++ STL中的priority_queue和map实现高效的贪心策略,并处理模运算下的概率计算问题。

1. 问题背景与核心思路

随机栈问题描述了一个包含2n次操作的场景:每次操作要么将一个数字放入集合,要么从当前集合中取出一个数字。最终要求取出的数字序列严格递增的概率,结果需要对998244353取模。

这个问题的核心在于:

  1. 贪心策略:每次取出当前集合中最小的数字,确保序列递增
  2. 概率计算:每次操作时,选择最小数字的概率等于当前集合中最小数字的个数除以集合大小
  3. 模运算处理:由于概率涉及分数,需要使用模逆元进行计算
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_queue<int, vector<int>, greater<int>> min_heap;

每次插入操作直接将数字压入堆中:

min_heap.push(x);

2.2 使用map维护数字出现次数

为了统计每个数字在当前集合中的出现次数,我们使用map来维护:

unordered_map<int, int> count_map;

当插入数字x时:

count_map[x]++;

当取出数字时,我们需要知道当前最小数字的出现次数:

int min_val = min_heap.top(); int cnt = count_map[min_val];

3. 贪心策略的详细实现

贪心算法的正确性基于以下观察:

  • 如果当前取出的数字小于之前取出的最大值,则无法形成递增序列,概率为0
  • 否则,每次必须取出当前最小的数字才能保证后续可能形成递增序列

实现步骤:

  1. 初始化最大值为0,分子和分母的累积变量
  2. 遍历所有操作:
    • 如果是插入操作,更新堆和计数
    • 如果是取出操作:
      • 检查当前最小值是否大于之前最大值
      • 更新概率的分子和分母
      • 从堆中移除该数字
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 常见错误分析

  1. 贪心策略错误:尝试其他取数策略而非总是取最小值
  2. 概率计算顺序错误:在模运算下,必须先计算分子分母的积,最后统一求逆元
  3. 堆与计数不同步:取出数字后忘记更新计数或堆

5.2 性能优化

  1. 提前终止:一旦发现当前最小值小于之前最大值,立即返回0
  2. 批量处理:可以累积计算分子分母,减少模运算次数
  3. 内存预分配:预先分配足够空间给堆和map,避免动态扩容开销
// 优化:预分配内存 min_heap.reserve(n); count_map.reserve(n);

6. 扩展应用与类似问题

这种结合贪心策略和模运算的技巧在竞赛中非常常见,类似的问题包括:

  1. 概率期望问题:需要计算大量概率的乘积并对大质数取模
  2. 贪心选择问题:需要动态维护当前最优选择
  3. 数据结构综合应用:同时需要堆和哈希表的功能

一个类似的经典问题是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; vector<int> operations(2*n); for(int i = 0; i < 2*n; i++) { cin >> operations[i]; } priority_queue<int, vector<int>, greater<int>> min_heap; unordered_map<int, 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. 进阶思考与变种问题

理解了基础解法后,可以考虑以下变种问题:

  1. 操作序列不确定:如果操作序列是动态生成的,如何在线处理?
  2. 多条件约束:除了严格递增,还需要满足其他条件(如奇偶性)
  3. 更大数据范围:当n达到1e6时,如何进一步优化?

对于第一个变种,可以考虑使用树状数组或线段树来维护动态集合的统计信息。对于第三个变种,可能需要优化哈希表的实现或使用更高效的数据结构。

在实际比赛中,理解问题本质并选择合适的数据结构往往比编码本身更重要。这道题很好地展示了如何将数学知识与数据结构结合来解决复杂问题。

http://www.jsqmd.com/news/713758/

相关文章:

  • 告别手动配置:用脚本自动化部署S32K144的AutoSAR MCAL开发环境(附GitHub仓库)
  • 资源共享实践:汽车行业如何构建高效的ANSYS仿真许可证池
  • 控油洗发水哪个更靠谱?核心选购标准与浅香品牌深度解析 - 博客万
  • Qt 6.5.3 踩坑记:新项目里自定义QML组件为啥总提示 ‘is not a type‘?
  • Radeon Software Slimmer:让AMD显卡驱动轻量化的智能解决方案
  • 终极实战指南:从零精通英雄联盟智能助手League Akari
  • DeepSeek V4 深度测评:代码生成能力能否超越GPT-4o?
  • TranslateGemma多模型对比评测:4B/12B/27B版本性能差异深度分析
  • 扩散模型在CT重建中的技术解析与应用实践
  • 2026最新温泉养生/温泉度假/冰雪温泉旅游打卡推荐!吉林优质权威榜单发布,口碑佳延吉长白山等地打卡好去处 - 博客万
  • Cursor Free VIP:AI编程助手试用限制的智能绕过解决方案
  • MySQL 查询缓存与执行计划交互机制
  • 为什么92%的AI工程师还在用2024旧版?Docker AI Toolkit 2026新增RAG流水线一键容器化模块,3行命令启动私有知识库
  • 从一次容器调试实战,搞懂Docker Seccomp:如何用`strace`和`docker inspect`排查被禁用的系统调用
  • 2026年探讨西宁买正宗青藏特产店,哪家更值得推荐 - 工业品网
  • 声明式光标控制库:提升输入交互体验的工程实践
  • Redis发布订阅与消息队列实现
  • 2026最新女装牛仔布源头厂家推荐!国内优质权威榜单发布,广东佛山等地高性价比厂商精选 - 十大品牌榜
  • 双边丝护栏网厂家评测:哪家更适合光伏电站防护? - 博客万
  • 任务拆解基础:复杂需求如何被 Agent 分步执行
  • 从Polkit策略入手,彻底搞懂xrdp远程桌面为何总弹出权限验证
  • 2026年北京口碑好的合同纠纷正规律师团队推荐,专业服务全解析 - 工业品网
  • 掌握Linux键盘音效定制:keysound让你的打字体验焕然一新
  • Nginx报错111: Connection refused?别慌,5分钟排查upstream连接失败的保姆级指南
  • 如何3步解锁Cursor Pro永久免费:开源破解工具深度解析
  • create certificate on Linux by script ( Method 1)
  • 避免gpu监控占用业务显存
  • 保姆级教程:拆解ICode Python函数题,从Dev.step到带参函数一次搞定
  • 从Github到客户验收:一个EIS防抖项目的完整踩坑复盘与性能调优指南
  • 2026年儿童数字健康守护公司推荐,青禾序儿童数字健康关心公司靠谱吗 - 工业品网