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

算法竞赛选手必看:ICPC香港站H题Mah-jong的三进制状压与双指针解法详解

算法竞赛选手必看:ICPC香港站H题Mah-jong的三进制状压与双指针解法详解

麻将牌型判断一直是算法竞赛中极具挑战性的问题类型,它不仅考察选手对动态规划、状态压缩等经典算法的掌握程度,更考验将实际问题抽象为数学模型的能力。在2024年ICPC香港区域赛的H题Mah-jong中,命题人巧妙地将麻将的"碰"和"吃"规则转化为三进制状态压缩问题,结合双指针技术实现高效求解。这道题的正解率不足15%,成为区分金牌队伍的关键题目。

1. 麻将规则与问题抽象

麻将的牌型判断核心在于处理两种基本操作:碰(三张相同数字牌)和吃(三张连续数字牌)。在算法设计中,我们需要将这两种操作转化为可计算的数学模型。

  • 碰操作:三个相同的数字牌(如三个"1万")
  • 吃操作:三个连续的数字牌(如"1万、2万、3万")

题目要求计算所有满足条件的子区间,其中每个数字牌的使用次数必须是3的倍数(可以同时用于碰和吃)。例如数字"2"可能被用于:

  • 一次碰(消耗3个"2")
  • 或参与三个不同的吃组合(如"1-2-3"、"2-3-4"、"2-3-4"各消耗1个"2")

1.1 三进制状态设计

传统状态压缩常用二进制(每位0/1表示存在与否),但本题需要记录每个数字出现次数模3的余数(0/1/2),因此采用三进制:

# 三进制状态示例:数字1-8的出现次数模3 state = 0 for num in range(1, 9): state = state * 3 + (count[num] % 3)

这种表示法将8个数字的状态压缩为一个0~3⁸-1的整数,极大减少了状态空间。实际解题中发现,连续数字的吃操作只涉及相邻6个数字(如"1-2-3"到"6-7-8"),因此状态数可进一步优化为3⁶=729种。

2. 双指针滑动窗口优化

直接枚举所有子区间时间复杂度为O(n²),对于n=1e5的数据显然不可行。我们需要利用双指针技术将复杂度降为O(n)。

2.1 滑动窗口的条件维护

核心观察:当固定右指针i时,左指针l需要满足对于所有数字j:

tong[i][j] - tong[l-1][j] ≡ g[j] (mod 3)

其中:

  • tong[i][j]是前i个元素中数字j的出现次数
  • g[j]是当前枚举的吃组合对数字j的需求量

实现时使用哈希表h记录前缀状态出现次数:

vector<int> h(100000, 0); for (int i = 0; i <= n; i++) h[f[i]]++; // 记录初始前缀状态 int l = 0; for (int i = 1; i <= n; i++) { while (l <= i) h[f[l++]]--; // 移动左指针,剔除无效状态 int s = 0; for (int j = 1; j <= 8; j++) { while (l <= n && tong[l][j] < tong[i-1][j] + g[j]) h[f[l++]]--; // 确保窗口内数字j足够满足g[j]需求 s = s * 3 + (tong[i-1][j] + g[j]) % 3; } if (l > n) break; ans += h[s]; // 统计匹配状态数 }

2.2 复杂度分析

  • 外层循环:729种吃组合
  • 内层双指针扫描:O(n)
  • 总复杂度:O(729*n),在n=1e5时约7e7次操作,实际运行时间约300ms

3. 关键实现细节与优化技巧

3.1 前缀和数组的高效处理

预处理tong数组加速区间数字计数查询:

vector<vector<int>> tong(n + 1, vector<int>(10, 0)); for (int i = 1; i <= n; i++) { for (int j = 1; j <= 8; j++) tong[i][j] = tong[i-1][j]; tong[i][a[i]]++; }

3.2 状态压缩的位运算技巧

虽然使用三进制,但实际编码时通过乘法和取模运算实现:

vector<int> f(n + 1, 0); for (int i = 1; i <= n; i++) { int state = 0; for (int j = 1; j <= 8; j++) state = state * 3 + (tong[i][j] % 3); f[i] = state; }

3.3 吃组合的枚举与需求计算

6个连续数字的吃组合(如1-2-3到6-7-8)对应三进制数0-728,分解每位表示该吃组合出现的次数模3:

vector<int> g(10, 0); int t = bit; // 当前枚举的吃组合状态(0-728) for (int i = 1; i <= 6; i++) { int x = t % 3; // 第i个吃组合的次数 t /= 3; g[i] += x; // 数字i的需求 g[i+1] += x; // 数字i+1的需求 g[i+2] += x; // 数字i+2的需求 }

4. 同类问题的扩展应用

这种三进制状压+双指针的技术可以推广到许多需要满足模数条件的子区间统计问题:

  1. 字符频率统计:如寻找子串使得各字母出现次数满足特定模关系
  2. 资源分配问题:多类资源的分配需要满足某些周期性条件
  3. 游戏状态判断:卡牌游戏中特定组合的检测

实际应用时需要注意:当状态空间过大(如模数较大或维度较高)时,可能需要结合哈希或其他优化技术减少内存使用。

在训练这类题目时,建议从简单版本入手:

  • 先解决二进制状态压缩问题(如LeetCode 1371)
  • 再尝试固定窗口大小的模数问题
  • 最后挑战这种动态窗口+多模数条件的复杂变种

ICPC这类高水平竞赛中,出题人常将多个经典算法组合创新。这道H题的精彩之处在于:

  1. 将麻将规则转化为模数学问题
  2. 通过三进制压缩将指数级状态变为可处理规模
  3. 用双指针维护动态变化的模条件
  4. 最终实现看似O(n³)问题的高效O(n)解法
http://www.cnnetsun.cn/news/1768893.html

相关文章:

  • OpenClaw技能组合:Kimi-VL-A3B-Thinking与其他AI模型的管道协作
  • 保姆级避坑指南:在只有一台能上网的服务器上,搞定Proxmox VE 7.0三节点集群和Ceph存储
  • 深入剖析FlashDB TSDB:嵌入式时序数据存储实战指南
  • 1个网关=100+设备兼容:耐达讯自动化CC-Link IE 转 EtherCAT重新定义工业协议转换价值
  • Windows更新修复工具深度技术指南:从问题诊断到系统优化
  • 空间智能底座:破解数字孪生困局,构建可计算物理世界
  • Windows 11终极优化指南:使用Win11Debloat实现系统性能提升的完整教程
  • 什么是MVP? 在项目里如何使用?
  • 实战指南:基于STM32F411CEU6的LED灯控制与按键交互实现
  • Micro-ros实战指南:在STM32平台从零构建自定义消息的ROS2节点
  • claw-code 源码分析:API Client 抽象——多提供商、OAuth、流式响应的统一接口长什么样?
  • 别再写10个函数了!用Arduino数组驱动数码管,代码量减半的秘密
  • 【权威实测|2026.03.15 CPython核心团队签发】:Python原生AOT插件下载失败率骤降92%,但90%开发者仍卡在第2步安装验证
  • 别再只会点鼠标了!用ComfyUI节点搭建你的第一个AI绘画工作流(附避坑清单)
  • KDD 2025前瞻 | 时间序列前沿:从预测、异常检测到测试时适应的核心突破
  • 【高并发DOTS网络同步终极方案】:单服2000实体毫秒级状态同步的确定性帧同步架构,含NetworkStream+JobChunk双缓冲实现
  • 【微软内部泄露文档】:Blazor 2026插件安装失败率高达63.8%?一文破解.NET SDK 9.0.100+环境下的静默崩溃根因
  • 沃思智能路灯改造方案:让城市照明省电50%的科技秘籍
  • 终极模组管理器:XXMI启动器让多游戏模组管理变得简单高效 [特殊字符]
  • Java final关键字与抽象类深度解析
  • 从音频降噪到图像滤波:傅里叶、拉普拉斯、Z变换在实际工程中的选择指南
  • 告别重复搬砖!OpenClaw从零搭建可操作系统级AI智能体,自动化提效10倍实战指南
  • CLion 2025.1.1 + CubeMX + CMake:一站式配置STM32调试与烧录环境(以F103C8T6为例)
  • 使用 Deepseek 识别招聘陷阱(以卖保险为例)
  • 蕙兰瑜伽与素食,让程序员告别亚健康的生活方式
  • DeepFlow Agent 故障排查指南:注册失败、协议解析、资源识别与配置方式谛
  • 3分钟掌握网盘直链下载助手:免费高速下载六大网盘的终极方案
  • RK芯片定制化armbian系统:从根文件系统到GPU驱动优化
  • Seata部署后TC、TM、RM总报错?从日志和监控面板快速定位问题(附常见坑点)
  • 别再乱删了!手把手教你用官方工具彻底卸载Autodesk全家桶(3ds Max/CAD)