跳转至

抽奖插件:题解

解法比较

出题人解法按“当前值、编号”把用户放进随机跳表。查询目标的前驱和后继,就能按距离与编号规则选出获奖者;更新时删除旧键再插入新键。距离随有序键单调变化,所以无需检查更远的键,证明见下文。理想随机层高的跳表每次查找期望为 \(O(\log n)\) ;出题人实现固定最大层数,最坏一次查找仍可能为 \(O(n)\) ,不能写成确定的对数保证。

Codex(GPT-6)采用的实现用标准有序集合实现相同操作,单次最坏为 \(O(\log(n+1))\) ;还按操作时刻合并输入序列,不保存长度为 \(m\) 的完整事件数组。总时间为 \(O(n+m\log(n+1))\) ,空间由完整事件表加节点和输入的 \(O(n+m+q+k)\) 降为 \(O(n+q+k)\) .收益是容器维护更少、复杂度保证更直接;有序树节点分配仍有常数开销,未实测比较两者速度。

按二元组维护在场者

用有序集合保存 \((\text{幸运数字},\text{编号})\) ,按字典序排列。再用数组记录每个人最近一次设置的幸运数字;退出后仍保留这个值,方便删除操作直接寻找对应二元组。

修改时先删除旧二元组,再插入新二元组;旧二元组不在集合中时,删除自然不产生影响。退出事件也是删除对应二元组,所以重复退出无需特殊分支。

查询只看两个人

查询目标为 \(t\) 时,找到第一个大于 \((t,n+1)\) 的二元组,记为右候选。它是幸运数字大于 \(t\) 的人中,幸运数字最小、且编号最小者。其前驱为左候选,是幸运数字不超过 \(t\) 的人中,幸运数字最大、且编号最大者。

任何其他人都不可能优于这两个候选:在同一侧离 \(t\) 更远,或者在相同幸运数字下编号不符合规则。比较两个候选的距离,距离相同时选择左候选,因为其幸运数字更小。仅存在一侧时选择该侧;集合为空时输出零。

这里必须按完整二元组排序,不能只保存每种幸运数字下的一个固定代表,因为查询值在该幸运数字两侧时,所需编号方向相反。

事件与数值

查询位置和修改位置分别已经递增,用两个指针即可判断当前事件类型。每个事件开始时先推进一次随机序列,再执行对应操作。

首次乘法中 \(A,B\le10^9\) ,\(AB+C\le10^{18}+10^9\) ,64 位有符号整数足够。后续序列项不超过 \(n\) .不要用 32 位整数计算乘积。

初始二元组按递增顺序插入 std::set ,使用尾部提示迭代器,可使初始化摊还线性。其余操作每次 \(O(\log(n+1))\) ,总时间 \(O(n+m\log(n+1))\) ,空间 \(O(n+q+k)\) .实现不保存全部 \(m\) 个目标值或事件类型。

正确性与验证

归纳每个事件后,集合恰好包含所有在场者的当前二元组。查询候选论证保证返回值遵守全部三级排序规则,因此所有事件的输出正确。

独立模型使用字典保存所有在场者,每次查询直接按题面三项优先级扫描。对拍覆盖相同幸运数字、目标相等、等距离、退出后重入、重复退出、空集合、无事件及生成参数上界。

程序与复现

参考程序见 std.cpp。使用 C++17 编译后,可用 tests/generate.py --out-dir <题目目录以外的路径> --std <程序路径> 生成输入和答案。固定种子与用例目的见 生成计划。性能测量见 评测环境与性能。