基本思路
明示了在二进制下考虑问题,我们大体的思路就是从高往低依次确定最大的数二进制下每一位上的值。
以下所述的「前缀」均指一个二进制数从高位到低位的一部分,一个元素的「前 \(k\) 位」表示二进制从高位到低位的前 \(k\) 位,\(res\) 表示当前记录的最大前缀的长度。
先看看操作能干嘛,一是可以判断一个数和一个前缀的大小关系,二是在知道某数前 \(k\) 位的前提下可以得到第 \(k+1\) 位。
依次确定每个数的值肯定不行,我们要通过当前知道的最大前缀对不可能成为答案的数进行排除,在同一个数上过多地停留询问它的信息也是不好的,这样可能导致我们得到许多无用信息。
考虑这样的一个过程:扫一遍数组,每次更新一下当前的最长前缀,然后不论产没产生贡献都把它抛之脑后,相当于每个数最多对答案产生 \(1\) 位的贡献,反复进行多轮这样的操作直到得出答案。
构造解法
有没有一种构造完美契合上面的思路呢,答案是肯定的,具体来说我们维护一个栈,它需要满足以下条件:
- 栈中元素个数 \(=res\)。
- 栈中自底向上第 \(k\) 位元素的二进制前 \(k\) 位与当前最大前缀的前 \(k\) 位相同。
考虑如何在加入元素 \(a_i\) 的同时维护这个栈:
- 如果 \(a_i\) 的前 \(res\) 位大于当前确定的最大前缀,弹栈,重复进行这个过程。
- 如果 \(a_i\) 的前 \(res\) 位等于当前确定的最大前缀,确定 \(i\) 的第 \(res+1\) 位并更新最大前缀。
- 如果 \(a_i\) 的前 \(res\) 位小于当前确定的最大前缀,跳过 \(a_i\)。
当一轮扫描结束后,我们得到了当前的可能最大前缀。
为什么是可能最大前缀?原因就是上述算法中我们为了避免过多地获取某一个数可能无用的信息,每一个数对于 \(res\) 最多只有 \(1\) 位的贡献。
例如假设当前的栈的状态是像这样的,标红的是给最大前缀产生过贡献的位:
\[\begin{matrix}10{\color{Red}1}0\\1{\color{Red}0}01\\{\color{Red}1}111\end{matrix} \]怎么反例都是从题解嫖的啊
现在得到的最大前缀是 \(101\),然而最下面的元素显然有更大的前缀 \(111\)。
这种情况是十分好处理的,我们要求的是最大的前缀而不是最长的,为了保证正确性就算缩减这轮得出的最大前缀长度也是值得的。
栈顶到栈底顺序扫一遍维护:
- 如果 \(a_i\) 的前 \(res\) 位大于最大前缀,说明上方的最大前缀是假的,弹栈直到 \(a_i\) 为栈顶并更新最大前缀。
- 如果 \(a_i\) 的前 \(res\) 位小于最大前缀且不为栈顶,它一定不会成为答案也不会影响答案正确性
人畜无害好可爱,取出但不更新最大前缀。 - 如果 \(a_i\) 的前 \(res\) 位小于最大前缀且正好是栈顶,弹栈且更新最长前缀。
这样做完一遍之后可能最大前缀一定最大,把得出的最大前缀加入答案,把所有数减去最大前缀,反证可以证明此时剩下 \(>0\) 的数一定在栈中,反复执行以上过程直到得出答案。
分析每一轮中上述策略的操作次数,第一次扫描中弹栈每轮最多执行 \(n-res\) 次,每次加入一个元素需要 \(3\) 次询问,第二次从栈顶向下排除需要 \(res\) 次,总计 \(4n\) 次询问。除了第一轮外每次的 \(n\) 会变为上一轮的 \(res\),\(\sum res=b\),所以总询问次数时 \(4(n+b)\) 的。
优化
减少询问看看能在哪些操作上面动手脚,好像除了加入一个数时判定它是否小于当前最大前缀都是必需的,那就看啊可能把这个操作删去,在加入时只要不能弹栈就确定 \(res+1\) 位会不会对答案产生负面影响,答案自然是不会,因为小于的数要么被可能成为答案的数在加入时弹掉,要么在从栈顶开始考虑时被弹掉左右都对答案没啥影响,优化到 \(3(n-b)\) 次询问。