「USACO 2020.12 Platinum」Cowmistry
「USACO 2020.12 Platinum」Cowmistry
看到样例解释突然就有了思路.jpg
令 \(m=\min\{2^t|2^t>k\}\) ,也就是 \(k\) 最高位+1
对于数轴,按照 \([im,(i+1)m)\) 分组,显然跨过分组的数之间异或 \(\ge m>k\) ,不合法,扔掉
对于每组,直接看做是 \([0,m-1]\) ,此时令 \(d=\frac{m}{2}\)
将 \([0,m-1]\) 分为 \([0,d-1],[d,m-1]\) ,显然两段之内的数相互异或的结果 \(<d\) ,一定合法
也就是说,长度为 \(d\) 的段内随意选3个都合法
下面考虑跨过 \(d\) 的贡献,显然是一边选一个,一边选两个,此时这些数之间的异或和 \(\ge d\)
以左边选一个为例,令 \(k'=k-d\)
\(O(k\log k)\)
考虑异或和中一定有 \(d\) 这一位,下面只需要对于 \(i\in[0,d-1]\) 暴力统计出 \([d,m]\) 中有多少个数 \(j\) 满足 \(i\oplus j\leq k'\)
可以前缀和之后 \(\log k\) 做到,从高到低依次考虑 \(k\) 每一个为1的二进制位 \(i\) ,如果查询的数这一位和 \(x\) 相同,那么下面可以任意选择
否则将 \(x\rightarrow x\oplus 2^i\)
实现如下
1 | int Que(int x,int *A,int k){ |
得到个数 \(c\) 之后,接下来就是为 \(x\) 在 \(c\) 里选择两个匹配,就是 \(\sum \frac{c(c-1)}{2}\)
由此得到一个 \(O(k\log k)\) 做法,可以通过前三档数据
1 | namespace part_klogk{ |
\[\ \]
\(O(n\log k-n\log ^2 k)\)
考虑对于 \(k'\) ,问题降阶为求区间 \(a\) 中的每一个数 , 在区间 \(b\) 中查询合法的个数 \(cnt_a\)
其中 \(a,b\) 区间可以认为对应相同的 \([0,L-1]\) ,但是出现元素不同
此时,继续采用上面的方法进行分组,分组对象变为两组区间
令 \(m'=\min\{2^t|2^t>k'\},d'=\frac{m}{2}\) ,分组之后
对于 \([0,d'-1],[d',m-1]\) ,显然两段之内异或 \(\leq d\) ,一定合法,加入答案 \(cnt_a\) 中
对于跨过区间的贡献,用下面的方法处理
左边区间对于右边区间继续递归进行查询,将得到的结果加上左边区间中数的个数
右边区间继续对于左边区间递归进行查询,将得到的结果加上右边区间中数的个数
问题不断降阶为子问题,会有 \(\log k\) 层子问题
每层子问题所有的区间可以分为 \(O(n)\) 段 特殊 的段
其他的部分要么完全覆盖要么为空,这些部分可以快速求出
发现答案如果用 \((num,cnt)\) 表示当前查询结果为 \(num\) 的个数为 \(cnt\)
每层可以用 \(O(n)\) 个不同的二元对表示结果
如此求得后,再自底向上合并得到答案
\[ \ \]
\[ \ \]
关于实现
如果真的按照上面的方法,一层层向下分裂区间,会面临着常数大,难以实现的问题
考虑将每个区间 \([L_i,R_i]\) 插入线段树 \([0,2^{30}-1]\)
显然得到 \(O(n\log k)\) 个节点,在底层完全覆盖的节点打上标记
先递归进行第一层的分裂区间操作,对于打上标记的节点可以分成 \(\frac{r-l+1}{m}\) 个完全覆盖区间
这些完全覆盖区间的答案可以 \(O(1)\) 求出
\[ \ \]
分裂完成后,每次查询的区间对用 \((a,b,L,k,f1,f2)\) 表示
其中 \(a\) 表示查询区间对应节点, \(b\) 表示被查询区间对应节点
\(L\) 表示区间长度, \(f1,f2\) 表示 \(a,b\) 是否继承到上层的完全覆盖标记
如此每次合并 \((ls_a,rs_b),(rs_a,ls_b)\) 的查询结果,加上另一边的贡献 即可
当 \(b\) 为空或者为完全覆盖时,答案可以 \(O(1)\) 得到
当 \(a\) 为空时,可以得到空答案
从这样的底层向上合并,每个元素被合并次数 \(O(\log k)\)
\[ \ \]
没有仔细分析复杂度,应该就是 \(O(n\log k-n\log ^2 k)\)
1 |
|