可以。我得到了一个确定性、最坏情况下 (C\le 27) 的构造,并写出了完整实现。
它是在公开的 (C\le28) 方案所用的“链分解、已知骨架标记、单点叶子子集计数”框架上,合并三处改进得到的。(QOJ)
这不是随机测试得到的经验上界:下面的成本公式已经对全部 (1544) 种必要参数情况精确枚举,特殊编码矩阵也已穷举验证。不过,我没有提交官方评测,也没有证明 (27) 最优。
完整 C++ 实现 · 独立上界验证脚本 · 完整代码包,含本地模拟器与说明
下面重点讲清楚:为什么可以从 (28) 降到 (27)。
一、沿用的基础框架
依次将叶子 (i) 灌到 insert 返回 false,记成功次数为 (\ell_i)。这次新增的结点形成一条以叶子 (i) 结尾的链,各链不相交,覆盖整棵树,因此
$$ N=\sum_i\ell_i. $$
称 (\ell_i\ge2) 的链为长链,(\ell_i=1) 的链为单点叶子。所有内部结点都在长链中。
首轮对前 (8) 条链分别使用颜色 (8,7,\ldots,1),其余使用 (0)。一次 collect 后,既知道所有链长,也能恢复前 (8) 条链。令
$$ D=\text{全部长链数},\qquad a=\text{前 \(8\) 条链中的长链数}, $$
那么尚未定位的单点叶子数是
$$ s=M-D-8+a. $$
当 (M\le8) 时,直接给所有链使用递减的不同颜色,一轮就能恢复,(C=M\le8)。(QOJ)
后面还用到原方案的一个骨架标记引理:
已知 (d) 条长链时,适当省略已知叶子,可以用
$$ b=\lceil\sqrt d\rceil $$
种颜色标记已知骨架,使同一父结点的内部子结点,可以通过“自身颜色、其第一个骨架子结点的颜色”唯一识别。这不依赖同值兄弟的遍历顺序。(QOJ)
以下是新的部分。
二、改进一:长链颜色对从“矩形”扩成“三角形”
固定全局最大球值 (B)。当前已经恢复 (d) 条长链,令
$$ b=\lceil\sqrt d\rceil,\qquad h=B+1-b. $$
将已知骨架的颜色整体平移到
$$ h,h+1,\ldots,B. $$
我们还允许预留 (f) 种最低颜色
$$ 0,1,\ldots,f-1 $$
用于单点叶子的观测;普通补链轮次取 (f=0)。
新的链编码
一条新长链使用颜色序列
$$ \boxed{x,\ y,\ B,\ B,\ldots} $$
其中
$$ f\le x
关键安排:按链首颜色 (x) 非增的顺序枚举颜色对,再依照原叶子顺序分配给新长链。
因此,一轮可使用的不同颜色对数量是
$$ \begin{aligned} A(B,b,f) &=\sum_{x=f}^{B-b}(B-x)\\ &=\boxed{T(B-f)-T(b-1)}, \end{aligned} $$
其中
$$ T(z)=\frac{z(z+1)}2. $$
没有提前观测时,容量就是
$$ \boxed{A(B,b,0)=T(B)-T(b-1)}. $$
例如 (B=17,b=3),容量为
$$ 153-3=150. $$
为什么这样还能解析?
考虑链首颜色为 (x) 的一条链。
任何挂在它内部结点上的其他新链,都比它更晚加入。所以根据颜色对的分配顺序,那些侧挂链的链首颜色都满足
$$ x'\le x. $$
与此同时,当前链的后续结点颜色严格大于 (x):
$$ y>x,\qquad B>x. $$
于是,读到链首 (x) 后,可以:
先递归解析所有链首颜色不超过 (x) 的侧挂链,以及颜色小于 (f) 的单点叶子;接下来遇到的第一个大于 (x) 的值,就是这条链第二个结点的 (y)。
颜色对 ((x,y)) 唯一确定链身份,也就确定了链长。随后按已知长度继续解析即可。
这里有两个很容易写错的地方:
- 链首必须按非增顺序分配。否则侧挂链的链首可能超过当前链首,无法区分侧挂分支和链的延续。
- 解析新链时使用局部边界 (x),不能一直使用全局边界 (h)。第二个颜色 (y) 完全可以小于 (h),它仍然是链的延续。
这就允许使用整个三角形区域
$$ x
而不是要求第二个颜色一定属于已知骨架的高位颜色区间。
三、改进二:最后一次补长链,同时做第一次单点计数
单点叶子不一定要等到下一轮才开始处理。
在最后一次补长链的轮次中,当全部长链的球都放好以后,所有内部结点已经被占。此时再向一个尚未定位的单点叶子插球,球一定留在该叶子上。
因此,这一轮既可以恢复最后的长链,也可以为单点叶子做一次计数观测。
预留的 (f) 种低位颜色,正是用于这件事。因为它们比所有新链链首颜色都小,解析时可以直接把它们识别为单点叶子,并统计它们挂在哪个内部结点下面。
合并后能省多少?
全部长链恢复以后,骨架需要
$$ t=\lceil\sqrt D\rceil $$
种颜色,所以每个后续纯计数轮次有
$$ q=B+1-t $$
种颜色可用于单点叶子。
定义 (F(r)) 为:
使用同一种颜色进行 (r) 次子集计数,可以唯一识别一个组中至多 (F(r)) 个叶子的归属。
假设最后补长链时预留了 (f) 种颜色,后面再做 (r) 轮纯计数。
那么 (\min(q,f)) 个组能得到 (r+1) 次观测,其余后续组得到 (r) 次观测。若 (f>q),额外的提前颜色还可以各自直接定位一个叶子。
总容量为
$$ \boxed{ G(q,f,r) = qF(r) +\min(q,f)\bigl(F(r+1)-F(r)\bigr) +\max(0,f-q) }. $$
特别地,
$$ G(q,f,0)=f. $$
这次合并不是要求所有组都提前观测。只让一部分组多获得一行编码,就足以改善最坏边界。
四、改进三:用九次计数识别十六个变量
这里的计数是整数计数,不是模 (2) 运算。
设某一组有 (g) 个未知单点叶子。对每个内部结点 (u),用
$$ z\in\{0,1\}^g $$
表示组内哪些叶子的父结点是 (u)。
选择一个二进制矩阵
$$ A\in\{0,1\}^{r\times g}. $$
第 (k) 轮插入第 (k) 行中取值为 (1) 的那些叶子,就会在 (u) 处观察到
$$ Az. $$
因此需要
$$ Az=Az'\Longrightarrow z=z', \qquad z,z'\in\{0,1\}^g. $$
原方案的一般构造提供
$$ W(r)=\sum_{x=1}^{r}\operatorname{popcount}(x) $$
列,其中 (W(8)=13,\ W(9)=15)。(QOJ)
我找到并验证了一个 (9\times16) 的矩阵。因此这里改为
$$ \boxed{ F(r)= \begin{cases} 16,&r=9,\\ W(r),&r\ne9. \end{cases}} $$
矩阵的完整常量
把每列视为一个九位二进制整数,最低位对应第 (0) 行,十六列依次为:
466, 231, 382, 476, 441, 391, 459, 210,
364, 62, 470, 341, 59, 60, 91, 306
它的全部
$$ 2^{16}=65536 $$
个整数向量子集和两两不同。
下面这段代码就能独立验证,不使用求解器,也不依赖随机测试:
columns = [
466, 231, 382, 476, 441, 391, 459, 210,
364, 62, 470, 341, 59, 60, 91, 306,
]
sums = {(0,) * 9}
for mask in columns:
column = tuple((mask >> row) & 1 for row in range(9))
added = {
tuple(x + y for x, y in zip(old, column))
for old in sums
}
assert sums.isdisjoint(added)
sums.update(added)
assert len(sums) == 65536
矩阵是离线搜索得到的固定常量,最终算法不进行随机搜索。
实现中,九个计数分别用五个二进制位保存,然后穷举至多 (65536) 个子集精确查表。保存的是完整键值,不依赖随机哈希不碰撞的假设。其他行数仍用一般构造的直接解码。
五、如何严格得到最坏 (C\le27)
前面的改进给出了每轮的精确能力,剩下只是一个很小的整数动态规划。
1. 普通补长链轮次
枚举
$$ 8\le B\le26. $$
令
$$ dp_B[d] $$
表示从首轮已知的 (a) 条长链出发,额外恢复到 (d) 条长链最少需要多少次 collect。
初值
$$ dp_B[a]=0. $$
在状态 (d),令
$$ b=\lceil\sqrt d\rceil. $$
当 (b\le B) 时,一轮能增加至多
$$ T(B)-T(b-1) $$
条长链,所以对所有
$$ d
转移
$$ dp_B[e]\gets \min\bigl(dp_B[e],dp_B[d]+1\bigr). $$
这里枚举所有批次终点,而不是假设每轮尽可能多加一定最优。
2. 最后一轮预留多少计数颜色?
假设最后一轮开始前,已知 (d<D) 条长链。
取
$$ b=\lceil\sqrt d\rceil, $$
并找最小整数 (n),使
$$ T(n)-T(b-1)\ge D-d. $$
由于本轮预留 (f) 种颜色后的容量是
$$ T(B-f)-T(b-1), $$
所以最大的可行预留量就是
$$ \boxed{f=B-n}. $$
要求 (f\ge0)。
之后,找一个 (r),满足
$$ G\left(B+1-\lceil\sqrt D\rceil,\ f,\ r\right)\ge s. $$
总成本为
$$ \boxed{ C\le B+2+dp_B[d]+r }. $$
其中两次固定的 collect 分别是首轮和最后的“补长链兼计数”轮次。
两个退化情况单独处理:
当 (s=0) 时,
$$ C\le B+1+dp_B[D]. $$
当 (D=a) 时,不需要再补长链,只需选择
$$ qF(r)\ge s, $$
得到
$$ C\le B+1+r. $$
3. 为什么只需检查 (1544) 种参数?
对 (M=200),枚举
$$ 1\le D\le200, $$
$$ 1\le a\le\min(8,D), $$
$$ s=200-D-8+a\ge0. $$
一共有 (1544) 组合法参数。
对每一组,按上述公式枚举 (B,d,r),全部都能找到
$$ C\le27 $$
的计划。
固定 (D,a) 后,较小的 (M) 只会减少 (s),不会增加所需轮数,因此也覆盖所有 (M>8) 的情况。
独立验证脚本的实际输出是:
matrix: 65536 / 65536 distinct integer subset sums
parameter cases: 1544
worst minimum C in this construction: 27
one worst parameter triple (D, a, s): (37, 1, 156)
ALL EXACT CHECKS PASSED
这里穷举的是成本公式的全部参数,不是若干棵测试树。具体树形与链长不参与这个成本上界,所以这个检查给出的是最坏保证。
六、一个能看出三项改进如何配合的例子
取
$$ M=200,\qquad D=65,\qquad a=8. $$
那么
$$ s=200-65-8+8=135. $$
选择
$$ B=17. $$
首轮后已知 (8) 条长链,所以
$$ b=\lceil\sqrt8\rceil=3. $$
在补长链的轮次中预留
$$ f=6 $$
种提前计数颜色。此时可以恢复的长链数量为
$$ T(17-6)-T(3-1) =T(11)-T(2) =66-3 =63. $$
而只剩
$$ 65-8=57 $$
条长链,因此一轮就能全部恢复,同时给六个单点叶子组做第一行观测。
之后骨架需要
$$ \lceil\sqrt{65}\rceil=9 $$
种颜色,剩下
$$ q=18-9=9 $$
种计数颜色。
再做八轮,就有:
$$ 6\text{ 个组}\times F(9) + 3\text{ 个组}\times F(8) = 6\times16+3\times13 = 135. $$
恰好定位全部单点叶子。
最终
$$ K=1+1+8=10, $$
所以
$$ \boxed{C=17+10=27}. $$
七、实现与验证情况
完整代码已经实现骨架染色、三角颜色对的递归解析、提前计数、矩阵解码及参数计划选择。恢复过程中只需确定各条链链首的父结点;链内父边由链长直接确定,内部编号可以按题意重新分配。
本地整流程验证通过了 (3689) 个用例,包括全部 (1544) 种参数组合的多种结构、链、星形、随机结构,以及打乱同值兄弟顺序的测试,实际最大 (C=27)。另有 (100) 个随机用例通过了 AddressSanitizer 和 UndefinedBehaviorSanitizer 检查。
其他资源也有直接上界。对于 (M>8),由于
$$ B\ge8,\qquad B+K\le27, $$
可得
$$ K\le19. $$
首轮至多调用 (N+M) 次 insert,后面每轮至多 (N) 次,因此
$$ \boxed{ \#\mathrm{insert}\le KN+M \le19\cdot1000+200 =19200 }, $$
远低于题面的 (500000) 次限制。每个放球阶段结束都会 collect,返回答案时机器为空。
代码包里的复现命令为:
python3 verify_bound.py
g++ -std=c++17 -O2 -DNDEBUG \
ballmachine_c27.cpp self_test.cpp -o self_test
./self_test 200
因此,目前这里得到的确切改进是:
$$ \boxed{\text{题述全部约束下,可以将确定性最坏上界从 }28\text{ 降至 }27.} $$
这是一个经过完整实现和有限证书验证的可达上界;并不意味着 (26) 不可能,也不意味着已经证明 (27) 最优。