11.14 —— (VP)2024icpc杭州
沈阳站赛前的最后一把 \(VP\) ,希望能在沈阳站偷到牌子qwq。。。
赛时 \(4\) 题,高罚时,输在 \(E\) 题开出来得太慢了。
\(A,K\) 纯签到。
\(E\): 显然所在楼层越高越有利,那么当前只要我们有免费上升的机会,就可以贪心地用掉这个机会。将所有区间按照左端点升序排序,遇到左端点比当前所在楼层小,右端点比当前楼层大的区间就直接使用,直到没有可再使用的区间。若还剩下一些左端点大于当前楼层的区间,那么没有办法,我们只能通过额外花费来到达这些区间。不断重复上述过程,直到到达可上升的最高点 \(\max_{i=1}^{n} r_{i}\)。此时,剩下没有使用的区间的左端点均比当前位置低,我们一定可以按照这些区间右端点降序的顺序依次免费使用这些区间。按照上述方式模拟即可。
\(H\):一道看着唬人但是不难的构造题,赛时最后 \(10min\) 出的,希望后天比赛也能有这样的运气和发挥。容易猜到让最长重链的链头作根节点一定最优。我们发现最长的重链如果只有唯一一条,就可以这样构造:让这条重链的链头成为根节点,其他的重链都接在这个链头下面。于是我们考虑出现多条最长重链的情况。
我们发现:如果存在一条非最长链可以接在根节点所在重链的重儿子下方,那么这个重儿子的子树大小至少为最长重链的长度。此时再将其他重链接在根节点下方,一定不会影响这个重儿子。容易发现,这条非最长链的长度必须 \(\leq L_{max} - 2\)。否则一定无解。具体细节见代码。
M. Make It Divisible
首先不难发现,原序列的最小值一定是所有数的倍数;且对于该最小值左右两侧的子区间也有类似的性质。如果学过笛卡尔树,就会知道这就是笛卡尔树的划分方式。因此需要对原序列求一棵基于下标为二叉搜索树,基于值为小根堆的笛卡尔树。
该笛卡尔树满足:任意结点的取值都是父节点的倍数。这等价于 \(gcd(a[u], a[fa[u]]) = a[fa[u]]\)(\(u\) 是序列下标);考虑到无论对于原序列如何操作,任意两个数的差分不变,我们将上式改写为:
于是 \(a[u] - a[fa[u]]\) 为定值,也就是任何一个非叶结点都是若干定值的约数。
综合来看,我们会得到:笛卡尔树的根结点一定是所有这些差分的约数,也就是它们的 \(gcd\) 的约数。于是,我们只需要枚举这些差分的 \(gcd\) 的所有约数,得到对应的 \(x\),并 \(O(n)\) 检验该笛卡尔树是否满足要求即可。
总复杂度 \(O(n * d(A) + \sqrt d)\)。
F. Fuzzy Ranking
不难想到在每个列表中的相邻元素之间建一条有向边,得到的有向图满足所有给定的约束关系。\((x, y)\) 是一对 fuzzy pair 当且仅当 \((x, y)\) 在同一个 SCC 中。
那么对于给定查询,我们只需要统计该查询中编号为 \(i\) 的 SCC 内点的数量 \(cnt_{i}\),那么答案即为:
直接暴力统计的复杂度为 \(O(nq)\)。但我们发现给定查询具有特殊性:一定是某个排名表中的某个区间,该区间内每一对相邻点在原图中一定存在一条有向边。
我们能得出结论:在每个排名表中,每个 SCC 内的所有点一定构成一段连续区间。 否则会导致 SCC 之间构成环的情况,一定非法。
于是我们发现:给定查询的区间一定包含若干个完整的 SCC 与至多两个不完整的 SCC。对于完整的 SCC 计数可以直接利用前缀和来优化统计;不完整的 SCC 可以处理每个 SCC 连续段内部的前缀和与后缀和,最后用左部分的后缀和与右部分的前缀和累加即为所求。
具体实现见 code。
B. Barkley III
区间按位与 和 单点赋值 操作可以直接利用线段树实现;考虑如何处理区间查询。
对于每个查询 \([l, r]\),只需要找到 \(a[l, r]\) 中恰好只包含 \(1\) 个 \(0\) 的最高二进制位,那么此时删除的数字就是确定的,也就是该二进制位恰为 \(0\) 的那个数字。否则删除任何数字都一样(全 \(1\) 则该位结果一定是 \(1\);含有 \(\ge 2\) 个 \(0\) 则该位结果一定是 \(0\),由二进制贪心可知我们要尽可能扩大高位的 \(1\)。)
如果将每个二进制位均单独开一棵线段树维护,复杂度是 \(O((n + q)\log n\log A)\) 的。而 \(n,q \le 10^{6}\),最多只能带一个 \(\log\)。因此不能拆位做,只能考虑如何正确地维护整个序列。
那么问题就变成了:在线段树中找到 \([l,r]\) 内恰好含有一个 \(0\) 的最高二进制位,并找到该位为 \(0\) 的那个数在线段树上的位置。
要想不带多余 \(\log\) 并找到线段树中的某个位置,只能考虑在线段树上二分来做;而如何确定区间内哪一位中恰好含有 \(1\) 个 \(0\),考虑如何设计新的信息并正确利用 pushup 函数维护:
设 \(f_{p}\):线段树结点 \(p\) 表示区间 \([tr_{p}.l, tr_{p}.r]\) 中:
- 恰好只有一个数是 \(0\) 的二进制位 \(c\),\(f_{u}\) 对应二进制位 \(c\) 为 \(1\);
- 否则 \(f_{u}\) 对应二进制位 \(c\) 为 \(0\)。
\(g_{p}\):线段树结点 \(p\) 表示区间 \([tr_{p}.l, tr_{p}.r]\) 的区间与运算结果。
在 \(pushup(lc, rc) \rightarrow p\) 过程中,我们有:
这一看着很传神的结论,使得我们实现了二进制状态压缩优化复杂度:无需对每一位单独计算,而是直接利用二进制计算将整体 \(\log A\) 位实现 \(O(1)\) 的转移。这也是本题最关键的优化。
于是对于每个查询,我们只需要知道查询区间对应的 \(f\) 值中的最高位 \(1\) 所在的二进制位,找到起点然后在线段树上二分即可。总复杂度 \(O((n + q) \log n)\)。
实现细节见 code。

浙公网安备 33010602011771号