题解:P8569 [JRKSJ R6] 第七学区
这绝对是一个非常好的题,中午去吃一顿饭的功夫就想出来这个小巧思。
思路
首先我们考虑按位或的性质。
假设有一个 0/1 数组。由性质得,如果一个区间只要存在 1,那么这个区间的贡献就是 1。定义 $f_i$ 表示以 $i$ 结尾的所有区间的贡献和,所以 $f_i$ 显然就是间 $[1,i]$ 中最后一个 1 出现的位置,答案就是 $\sum f_i$。回到原题,我们会很容易想到把每一个数拆位来计算贡献,对于每一位,从头到尾跑一遍。注意其实 $a_i$ 和 $f_i$ 其实没必要存下来,就可以省下来空间。时间复杂度 $O(n \log V)$,会拿到 30pts 的好成绩。
代码如下:
1 | using namespace READ; |
我们考虑优化。首先定义 $l_i$ 表示当前位置第 $i$ 个数位最后一个 1 出现的位置,那么对于当前这个位置的答案就是 $\sum 2^i \times lst_i$。我们发现拆数位比拆位置的位数多很多,那我们为什么不拆位置呢?所以我们定义 $c_i$ 表示位置二进制拆分后第 $i$ 位为 1 的每一个数位是否存在最后一个 1 的情况。我们发现按照状压的方法存,$c_i$ 天然地帮我们乘好了每一个数位的系数。最终这个位置的答案就是 $\sum 2^i \times c_i$(注意这里的 $i$ 表示拆分的位置,而不是数位)。
我们考虑怎么维护 $c_i$?过程可以分成两步,一步是删去新加数的数位上原来的贡献,一步是加上现在的贡献。假设当前位置上的值是 $x$,第一步给每一个 $c_i$ 执行 $c_i \leftarrow c_i \land \overline{x}$,第二步对于拆分位置后是 1 的 $c_i$ 执行 $c_i \leftarrow c_i \lor x$,最后把答案合并算一下就行了。满分代码如下:
1 | using namespace READ; |
时间复杂度 $O(n \log n)$,这个数据范围虽然看着十分恐怖,但是实际上只需要 1s 左右,跑得飞快。