USACO 2022 Dec 铂金组题解
有生之年终于 AK 一次 Pt 组了,发个题解玩玩。
T1 - Breakdown
大部分情况下,题目里若存在一个很小的 \(k\) 这样的角色,都是因为它在复杂度指数上(包括但不限于 \(2^{\operatorname{poly}(k)}\) 或 \(n^{\operatorname{poly}(k)}\))。
时光倒流变成加边。考虑折半,维护 \(1\) 到每个点 / 每个点到 \(n\) 边数为 \(1 \sim 4\) 时分别的最短距离。询问时 meet in the middle,\(\mathrm O(n)\)。
怎么维护?考虑到 \(k\) 大概率在指数上,而 \(k\leq 4\),一个朴素的方法是暴力枚举 \(k\) 步的每一步,\(\mathrm O(n^k)\)。但最多只能接受 \(\mathrm O(n)\) 时间内更新一次。
设加的边为 \(x \to y\)。枚举这条边是 \(k\) 条边中的哪条,更新。其实是能平均一次 \(\mathrm O(n^{k - 2})\) 更新而非 \(\mathrm O\!\left(n^k\right)\) 的,因为 \(x, y\) 本身就确定了两个点。另外,如果这条边充当第一条从 \(1\) 出发的边的话,只能多确定一个点,但 \(x = 1\) 的边本身就只有总边数的 \(\frac 1 n\),恰好抵消了。
这样 \(k = 4\) 时是平方,还是不行。再注意到 \(k = 4\) 时可以利用 \(k = 2, 3\) 的信息;而 \(k = 2\) 时 \(\mathrm O(1)\) 又有点浪费,可以升级一下,\(\mathrm O(n)\) 维护两两点之间跨两步的最短路。利用这些信息,分类讨论可知 \(k = 4\) 时加入的边无论充当第几条,都可以在 \(\mathrm O(n)\) 时间内完成更新。
总复杂度 \(\mathrm O(n^3)\)。
T2 - Making Friends
当奶牛 \(x\) 离开时,设其所有邻居从小到大依次为 \(y_1, y_2, \cdots, y_k\)。
如果 \(k = 0\) 直接跳过。
否则,将 \(y_i\) 互相连接,显然等效于将 \(y_1\) 与 \(y_{2 \sim k}\) 相连。这就是个邻接表的合并,线段树合并或 set
启发式合并即可。
方便起见,给边按 \(x \to y (x < y)\) 定向。每次奶牛离开时邻接表大小之和减去初始边数就是答案。
T3 - Palindromes
先考虑怎么算某个串的 cost,这部分比较简单。如果 G 和 H 的数量都是奇数,那就 -1。否则设其中 G 的位置依次为 \(p_1, p_2, \cdots, p_k\)。显然在最优移动的过程中不会改变两个 G 的相对位置,\(p_i\) 与 \(p_{k - i + 1}\) 配对。要将一对 \(p_i, p_j\) 移动到对称的位置,显然至少要花 \(f(i, j) = |(M - p_i) - (p_j - M)| = |2M - p_i - p_j|\) 步数,其中 \(M = \frac{1 + L}2\),\(L\) 为串长。所以 cost 的一个下界是
容易证明,这个下界能取到:只要从外层往内层依次对齐,将靠内的往外移,对齐总能进行。
-1 和 \(2 \nmid k\) 的那项容易先 \(\mathrm O(n^2)\) 处理掉。接下来考虑每一对 \(p_i, p_j (i < j)\) 的贡献。那就是所有不为 -1 的,让 \(p_i, p_j\) 配对的子串 \([l, r]\),令它们的 \(M = \frac{l + r}2\),对 \(f(i, j)\) 求和。
容易想到将 \(i + j\) 相等的对放一起处理,从外往内,每次加入所有 \((l, r) \in (p_{i - 1}, p_i] \times [p_j, p_{j + 1})\) 并查询贡献,这样每对 \((l, r)\) 只会被加入一次。如果 \(f(i, j)\) 不带绝对值的话,那直接做完了;可惜是带绝对值的,那只能把 \(2M\) 装到 BIT 里,分 \(2M \lesseqgtr p_i + p_j\) 两半算贡献,\(\mathrm O(n^2 \log n)\)。
奈何它 7500 也卡不了我。共要 \(\frac{c^2}2 + \frac{n^2}2\) 次 BIT 操作,其中 \(c\) 是 G 出现次数,常数极小!一个卡常技巧是:G 还是 H 不重要,可以让出现次数较少的作为 G,这样最多要 \(\frac 5 8 n^2\) 次 BIT 操作。
这个方法是可以改进为 \(\mathrm O(n^2)\) 的。注意到,每次查询的左右端点 \(p_i, p_j\) 分别单调,于是直接单点修改数组并暴力移动指针,就可以 \(\mathrm O(1)\) 修改,且每个 \(i + j\) 相等组的查询操作都在 \(\mathrm O(n)\) 时间内解决。