上提的第二步和第五步,如果 m-a<a,则只需要移动 m-a 个元素,然后交换一下 N 和 H 的作用即可。
加上这些优化,70 分到手。考虑正解。
考虑一个对两个栈的操作:给定栈 X,Y,重排两个栈的元素使得 \operatorname{max}\{X\}\leq \operatorname{min}\{Y\}。其中,\operatorname{max} 和 \operatorname{min} 都是针对颜色编号而言的。这个操作也需要借助另一个满栈 H 和一个空栈 N。注意到同一种颜色可能有很多球,不便于操作,我们可以把所有元素分成“大的一半”和“小的一半”,分别应该装进 Y 和 X 里。以下我们用“元素为大”、“元素为小”称呼。
数一下 X 中共有多少个元素为大,这个数字记为 a。
把 H 栈顶 a 个元素依次放入 N。操作后 H 有 a 个空位,N 有 m-a 个空位。
一直弹出 X 的栈顶元素,如果为大则放入 H,否则放入 N,直到 X 为空。这时 H 和 N 都恰好满了。
把 N 栈顶 m-a 个元素依次放入 X。
一直弹出 Y 的栈顶元素,如果为大则放入 N,否则放入 X,直到 Y 为空。这时 N 和 X 都恰好满了。
把 N 栈顶 m-a 个元素依次放入 Y,把 H 栈顶 a 个元素依次放入 Y。
把 N 全部 a 个元素依次放入 H。操作后 H 和 N 恢复了操作前的原样。
为了方便下文管这个操作叫“切分”,意思当然是把小的一半切出来。操作结束时,X 所有元素为小,Y 所有元素为大。这就达成了我们的目的。这个操作的步数是 4m+a\leq 5m。可以使用类似上面第三条常数优化,如果 m-a<a,则第一步和第七步只需要移动 m-a 个元素,然后交换 H 和 N 的作用即可。优化后移动次数不超过 \frac{9}{2}m。
有了这个操作,我们可以使用类似冒泡排序的方式把整个 n 个栈按照颜色排序,排序之后很显然每个栈里球颜色一样。排序的正确性参照冒泡排序很容易证明(最大的元素一定会冒到最右边),可以算出移动步数 \frac{9}{4}n(n-1)m,渐进复杂度相同的情况下常数比上面那个算法大了一倍多,只能过 40 分,看起来根本没用……
尝试换一种排序:归并排序。要解决的问题就是如何归并。类似普通归并的过程,维护两个指针 x 和 y,初始值在要归并的两个区间最左侧。把两个指针指向的栈分别称为 X,Y。如果 \operatorname{max}\{X\}<\operatorname{max}\{Y\},对 X,Y 执行切分,小的一半放到 X,然后把指针 x 递增,继续执行。否则把小的一半放在 Y,指针 y 递增。这样做是正确的,因为未归并区间最小的 m 个元素一定包含在了 X 和 Y 里。最后我们可以得到一系列从小到大的栈,不过它们并没有摆在它们应该在的位置上。于是归并的过程中记录一下每个栈应该在的位置,利用那个空栈进行整体移动,可以使用不超过 2L 次整体移动把它们摆好(L 是归并的区间长度)。于是,要归并一个长度为 L 的区间,要执行不超过 L 次切分和不超过 2L 次整体移动,总操作次数不超过 \frac{13}{2}Lm。