2023.12 做题纪要 #1

终于从学考中解脱出来了,做题纪要回归!

11 月下半个月发生的事情:考了个 NOIP,游记在这,然后全力备战学考了,所以半个月没做题。

本文大部分题的题单 To-do List #2。题单的第一个题在上一篇做题纪要的最后。

2023.12.10

起床铃换成了《明天会更好》,感觉比较神秘,不评价了。

P9353 [JOI 2023 Final] Modern Machine

不得不说 JOI 出的题的质量还是比较高的,不过我这题基本没有任何思路,鉴定为菜。

首先考虑一次操作干了什么,注意到我们只有在最后一次转弯后的覆盖是有意义的,那么实际上,将结果分为从左边出界和从右边出界,分别造成的修改是前缀染红与后缀染蓝。

那么考虑具体染了多少,大概手动模拟一下发现,如果在第 \(i\) 个位置操作,会将前 \(i\) 个蓝色染红或把后 \(n-i+1\) 个红染蓝。证明也很简单,以前一种情况为例,考虑左边与右边转弯的次数,左边遇到红色会转弯,后边遇到蓝色会转弯,设左边有 \(p\) 个蓝色,那么就有 \(i-1-p\) 个红色,于是会转 \(i-1-p\) 次弯,最后从左边出,说明在右边转了 \(i-p\) 次弯,于是右边覆盖了 \(i-p\) 个蓝色,于是总共覆盖的蓝色数就是 \(i\) 个。

那么暴力模拟这个过程就可以 \(O(nmq)\) 了,可以拿到 \(15\) 分。改成线段树上二分即可 \(O(mq \log n)\),可以通过第三个包,拿到 \(25\) 分。

考虑第四、五个包怎么做,存在一个 \(t \in [0, n]\),使得前 \(t\) 个为红色,剩下的都是蓝色。注意到,此时每次操作之后,原来的操作不会改变这个性质,仅改变了 \(t\),那么我们实际上只需要考虑维护 \(t\) 即可。

由于开始会把所在的格子染红,所以起点在红与蓝上的情况会不同,简单分情况考虑一下:

  • 若起点 \(i\) 为红色:
    • \(n - t \ge i\),则 \(t \gets t + i\)
    • 否则,\(t \gets t - (n - i + 1)\)
  • 若起点 \(i\) 为蓝色:
    • \(n - t - 1 \ge i\),则 \(t \gets t + i + 1\)
    • 否则,\(t \gets t - (n - i)\)

可以发现,上述操作实际上是在模 \(n + 1\) 意义下进行加 \(i\)\(i+1\) 的操作,我们可以写出一个函数 \(f_i(t)\),表示在 \(i\) 操作下 \(t\) 会变成什么,那么有:

\[f_i(t) = \begin{cases} (t + i + 1) \bmod (n + 1) & t < i\\ (t + i) \bmod (n + 1) & t \ge i \end{cases} \]

那么,我们要求的,实际上就是一个区间内这个函数的复合。对于 \(n = 10\) 的情况,发现我们可以直接维护所有函数值,直接上线段树维护,复杂度 \(O(nq \log m)\),可以通过第四个包,拿到 \(36\) 分。

直接维护函数值显然是很差的,我们能不能直接维护分段函数?看起来似乎复杂度爆炸,但是注意到我们只有查询操作没有修改操作,于是我们的线段树只要能建出来就行,而查询操作也不需要真的把函数复合找出来,因为我们只要求单点的值,所以查询的时候不需要将线段树上的若干个区间合并起来,于是我们不需要太关心合并两个区间的复杂度,不过我们还是需要关心分段函数的总段数的。

而注意到复合一次这个分段函数相当于进行 \(O(1)\) 次的裁剪拼接,这只会使分段函数增加 \(O(1)\) 段,于是 \(len\) 个函数的复合得到的是一个 \(O(len)\) 段的分段函数,那么我们就可以发现线段树上所有节点上的函数的段数之和是 \(O(m \log m)\) 的。由于合并的时候需要找到分割点的位置,需要二分,所以总建树的复杂度就是 \(O(m \log^2 m)\) 的,单次查询也是 \(O(\log^2 m)\) 的,所以总复杂度 \(O((m + q) \log^2 m)\),可以通过第五个包,拿到 \(62\) 分。

一般情况呢?注意到,任意局面进行若干次操作后,一定会变成上述的局面,而由于每次操作都是前缀染红与后缀染蓝,我们只需要维护前缀红的数量 \(p\) 与后缀蓝的数量 \(q\),如果某一时刻两者染的部分交叉了,那么说明此时已经变成了上述的情况。考虑每次操作,如果操作的点 \(i \le p\),覆盖前缀则 \(p\) 会多覆盖 \(i\) 个蓝点,覆盖后缀则一定会变成前面所述的情况,\(q\) 是同理的,于是对于操作的点在 \(p,q\) 之内的情况,所进行的操作就是一直增加 \(p+q\) 的总和,我们只需要找到什么时候能够交叉即可。

如果操作的点不在 \(p,q\) 之内似乎就不好办了,不过我们注意到,进行一次操作后,无论增长的是 \(p\) 还是 \(q\),它们都会翻倍,也就是说不在 \(p,q\) 内的点的操作只有 \(O(\log n)\) 次,于是我们只需要每次找到下一次不在 \(p, q\) 内的点,然后判断一下此时应该覆盖前缀还是后缀即可。

但是找 \(p,q\) 内的点是比较麻烦的,因为 \(p,q\) 一直在变。我们可以不严格找到所有 \(p,q\) 之内的点,而是只考虑小于等于 \(p,q\) 的最大的 \(2\) 的幂 \(x,y\),这样我们只要对每个可能的 \(x,y\) 预处理出 \(i \le x\) 或者 \(i \ge n - y + 1\)\(i\)\(n-i+1\) 的前缀和与下一个 \(x < i < n - y + 1\) 的位置,这样不在 \(x,y\) 内的点仍然只有 \(O(\log n)\) 个,这样我们就可以每次跳一整段,看跳完之后是否 \(p,q\) 仍然不交,不交则找到新的 \(x,y\) 然后继续跳,交了则二分找到段内什么时候开始相交,于是这部分就可以在 \(O(m \log^2 n + q \log n)\) 的时间复杂度内解决了。

那么只需要把两部分拼在一起就做完这道题了。复杂度两个 \(\log\)。代码略有点难写,不过我只写了 5KB,std 命名比较长而且好像写的麻烦了点所以写了 8KB。

2023.12.11

今天 NOIP 一等线出了,全国基准线 199,HE 153,非常有趣。HE 省队名额大概是会骤减不少的了,不过好像按照 HE 的水平来看反而省队名额趋于正常了(

好像今天 THUWC 开始报名了,不过教练不在我这所以我大概拿不到什么有意义信息,不过 THU 申报官网上已经有了。

img

但是 THU 你能告诉我 NOPI 是什么玩意吗????

GYM102896F Find a Square

做这题纯属因为觉得比较有意思,没有别的原因。

但是好像只能想到其中一小部分,果然我的水平不如 water235。

\(M\)\(p(x)\) 最大值。

首先答案显然就是要去找若干个质因子的次数之和。注意到如果一个质因子仅出现一次那么这个质因子是没有用的,所以只考虑出现至少两次的质因子。

假如我们现在有一个集合 \(Q\) 为出现至少两次的质因子,那么我们考虑如何计算答案。注意到,\(p(x)\) 是一个二次多项式,根据同余方程的一些理论,对于一个指数 \(p\)\(p(x) \equiv 0 \pmod p\) 的解数最多只有 \(2\) 个,这可以通过二次方程求根公式加二次剩余找出,但如果 \(2a \bmod p = 0\),求根公式是解不出来的,而对于这样的质数 \(p\) 仅有 \(O(\log a)\) 个,我们可以直接拿这些数去对所有的整数都试一遍,这部分复杂度 \(O(n \log a)\)。对于 \(Q\) 中剩下的数,我们只需要考虑所有是 \(p\) 的倍数的值,这一共有 \(O(\sum \lceil \frac{n}{p} \rceil) = O(n \log M + |Q|)\) 个。

那么现在问题就是 \(Q\) 中哪些数。由于我们现在仅考虑出现次数至少两次的数,那么我们可以分两种情况考虑:一个质因子仅在同一个数中出现过,或者一个质因子出现在超过两个数中。在此之前,我们可以先将所有 \(O(M^\frac 13)\) 的质因子给除掉,此时剩下的每个数中最多只有两个质因子。如果一个质因子在同一个数中出现,那么这个数一定是完全平方数,我们把所有的数判断一下即可,那么此时剩下的所有数要不然是大质数,要不然是两个不同的质数相乘。那么此时我们仅需要考虑第二种情况,也就是只关心所有出现在至少两个数内的质因子。

考虑两个数 \(p(i), p(j)\),假如两者均为 \(p\) 的倍数,那么其差一定也是 \(p\) 的倍数,那么作差后可以得到 \(p | a (i^2 - j^2) + b (i - j)\),即 \(p | (i - j) (a (i + j) + b)\)。注意到前者一定是 \(O(n)\) 级别的数,而后者一定形如 \(ax+b\),前者可以找出所有 \(O(n)\) 的质数加入 \(Q\),后者可以使用与二次类似的想法,对于任意一个质数 \(p\) 来说,\(a x + b \equiv 0 \pmod p\) 最多有 \(1\) 个解,那么我们可以枚举 \(O(n)\) 内的所有质数,将其除去,剩下的所有 \(ax+b\) 要不然是 \(1\),要不然剩下一个大于 \(O(n)\) 的质数,把这 \(O(n)\) 个质数加入 \(Q\) 即可,这部分与二次的复杂度一样,\(O(n \log n)\)

注意上述复杂度均没有乘上找一个数内有多少质因子的数量,不过这部分平均除的次数不多,所以忽略不记。

发现我连二次剩余都不会求,所以再写个二次剩余的做法:

求解 \(x^2 \equiv a \pmod p\)

欧拉判别法:若 \(a^{\frac{p - 1}{2}} \equiv 1 \pmod p\),则 \(a\) 是二次剩余,若 \(a^{\frac{p - 1}{2}} \equiv -1 \pmod p\),则 \(a\) 是二次非剩余。

考虑找到一个数 \(r\),使得 \(r^2 - a\) 不是二次剩余,那么我们可以扩域,定义 \(i^2 \equiv r^2 - a \pmod p\),一个数可以用 \(a + bi\) 表示,乘法容易写出。有结论:\(a \equiv (r + i)^{p+1} \pmod p\),即 \(x \equiv \pm (r + i)^{\frac{p+1}{2}} \pmod p\)

证明:

\[\begin{aligned} (r+i)^{p + 1} &= (r+i)^p (r+i)\\ &= (r^p + i^p) (r + i)\\ &= (r - i)(r + i)\\ &= r^2 - i^2\\ &= a \end{aligned} \]

第一步直接二项式定理容易得到,第二步是由欧拉判别法可得 \(i^p = i i^{p-1} = i (r^2-a)^{\frac{p-1}{2}} = -i\)

而由于二次非剩余有 \(\frac{p-1}{2}\) 个,所以期望随机两次即可找到一个二次非剩余,于是我们就可以在 \(O(\log p)\) 的复杂度内解决二次剩余问题。

P6730 [WC2020] 猜数游戏

简单题..?好像真的不难。

首先都有 \(a_k^m \bmod p\) 了,保证质数是 \(p^k\),还有个特殊性质是 \(3^i \bmod p\) 互不相同,原根都糊脸上了。把原根找出来,设它是 \(g\)

先考虑模数为奇素数 \(p\) 的情况,那么此时所有数都存在离散对数,那么跑个离散对数出来,设 \(a_i \equiv g^{b_i} \pmod p\)。考虑题目实际上就是,每个数有若干覆盖范围,对于所有可能集合用最少的数覆盖整个集合。发现取完离散对数之后,覆盖的数就是所有 \(\gcd(b_i, \varphi(p))\) 的倍数。注意到这是满足偏序关系的,即如果 \(i\) 能覆盖 \(j\)\(j\) 能覆盖 \(k\),那么 \(i\) 也能覆盖 \(k\),那么如果我们把覆盖的关系建成一个图,那么这个图一定是 DAG,那么问题就很简单了,我们只需要选择所有入度为 \(0\) 的点就行了。计算期望也就很简单了,根据线性性,我们考虑每个点入度为 \(0\) 的概率,发现只要所有入度都不在集合里就行,所以概率就是个 \(2^{-c}\) 一类的东西。那么只需要把所有连边关系找出来就行了。这里偏序关系需要特别定义一下,因为有可能两个 \(i,j\)\(\gcd(b_i, \varphi(p))\) 相同,此时我们定义编号较小的那个偏序关系较小,而 \(\gcd\) 不相同的定义 \(\gcd\) 之间的整除关系为偏序关系即可。\(O(n^2)\) 容易实现。

考虑模数为 \(p^k\) 怎么做,发现此时多出了一些数是 \(p\) 的倍数,这些数不存在离散对数,注意到 \(p\) 的倍数的次幂一定都是 \(p\) 的倍数,不是 \(p\) 的倍数的次幂也一定都不是 \(p\) 的倍数,所以两部分可以分开计算。考虑设 \(c_i\)\(a_i\)\(p\) 的质因子的次数,注意到它的次幂的 \(c\) 一定比当前大,于是这仍然是一个 DAG,于是我们可以使用同样的方法去计算,问题在于如何快速判断 \(a_i\) 能不能覆盖 \(a_j\)。注意到,如果 \(a_i\) 能覆盖 \(a_j\),那么假如 \(a_i ^ m = a_j\),那么 \(c_j = m c_i\),这样我们就可以快速得到 \(m\),那么判断就很容易了。

离散对数不能直接 \(O(p)\) 计算,会炸空间,写个 BSGS 即可,复杂度 \(O(n \sqrt{p} + n^2)\)

CF1110G Tree-Tac-Toe

这是真简单题。简单分类讨论即可。

简单分析下,发现这里面有极多先手必胜策略。

  • 如果有点的度数 \(\ge 4\),那先手必胜,先手下中间然后依次选叶子即可;
  • 如果有点的度数 \(=3\)
    • 如果这个点是白色,那先手必胜;
    • 如果有一个相邻的点是白色,那先手必胜;
  • 如果存在一条长度为 \(4\) 且中间一个点为白色,则先手必胜;
  • 如果存在一条长为 \(5\) 且中间一个点度数为 \(3\),则先手必胜;
  • 如果有相邻两个白色点则先手必胜。

在进行以上分析之后,发现,上述条件均不满足的情况,一定满足:

  • 只有叶子才可能是白色;
  • 大致形如一条链,只有链的两端有可能为以下三种情况之一:
    • 一个三度点,相连的点均无颜色;
    • 一个无颜色叶子;
    • 一个白色叶子。

注意需要把 \(n=4\),有一个三度点的情况特判掉。

那么接下来我们只需要分析这几种情况的先手必胜策略了。

  • 一条链,仅有一端是白色 / 没有白色:显然先手是没有必胜策略的,此时一定平局;
  • 一条链,两端都有白色:首先注意到 \(n=3\) 先手必胜,\(n=4\) 平局,猜测奇数必胜偶数必败。发现,如果是奇数,每次可以选一边靠近叶子的第三个位置,这样后手必须去堵上中间的位置,否则先手就胜利了,那么此时 \(n\) 减少 \(2\),说明一定可以先手必胜。同理,如果是偶数,先手选择任意一个位置,都会把链分成一奇一偶,而后手只需要堵在奇数的一边,这样奇数一边得到的是第一种情况,先手没有必胜策略,偶数一边递归,于是最终一定平局;
  • 一边是三度点一边是白色:与上一个情况分析类似,发现 \(n\) 为偶数时先手必胜;
  • 两边都是三度点:与上一个情况分析类似,发现 \(n\) 为奇数时先手必胜。

剩下的没提到的情况就全都是平局。

2023.12.12

HE 省队名额:7

好似,开香槟了

但是 NGOI HE 前十进了两个,这下成为女生竞赛强省了💦

CF1804G Flow Control

自己做出道 3500*,酷炫。

首先除以 \(2\) 可以敏锐的想到 \(\log\) 次,但是注意到这个 \(\log\) 是每次加成功之后有可能 \(\log\) 次之后才能除到 \(b\) 以下,并没有什么意义。

注意到这玩意看起来就不像是有什么优秀的方法维护,于是猜测一定存在循环节。注意到一件事情,虽然数会增大,但是数之间的差是不变的,而除以 \(2\) 后,这个差会变小,也就是说,这个差在除以 \(\log\) 次之后会变成 \(0/1\)!而注意到,相同的值之后永远不会变得不同,也就是说,在进行 \(\log\) 次操作后,所有的数一定会变成两个差 \(1\) 的数或者变成同一个数,在这之后显然就是存在循环节的了。

那么如果所有点开始结束时间相同,我们只需要暴力模拟前 \(\log\) 次,然后后面直接计算循环节即可。经过一些分析可以发现出现循环节时一个循环节内一定仅有一次除以 \(2\) 操作。但是考虑到此时相当于有 \(n\) 段不同的时间段,每段内数并不相同,于是这时候再暴力模拟就变成了 \(O(n^2 \log V)\) 的了,不能接受。

考虑优化上面的过程,注意到,如果我们定义一个数的势能为当前集合内第一个比它小的数与它的差,那么减数时势能总和不变,而每次操作之后势能减半,那么我们可以用并查集将值相等的数直接缩起来,然后再暴力模拟,这样均摊下复杂度就是 \(O(n \log V)\) 的了,再加上使用 map 或哈希表维护每个数出现了多少次,总复杂度就是 \(O(n \log n \log V)\)\(O(n \log V)\) 的了,可以通过。

P6729 [WC2020] 有根树

这题有点太牛了。只能想到两个 \(\log\) 的做法。

首先有一个显然的观察:如果有两个点 \(x, y\) 是祖先关系且 \(x\) 更浅,那么一定有 \(w_x > w_y\)。也就是说,我们在选取连通块 \(C\) 时,一定先选取大的才能选取较小的,而我们目的是让剩下的点中的最大值最小,这与我们从大往小选是契合的,于是我们实际上要做的,就是要从大往小选,使得 \(\max(|C|, \max_{i \not \in C} \{w_i\})\) 最小,这个函数显然是单峰的,最小值在 \(|C| = \max_{i \not \in C} \{w_i\}\) 处取到。那么我们只需要把 \(w_i\) 维护出来就做完了..?注意到如果每次三分的话复杂度至少两个 \(\log\),而且这还意味着我们需要在一个 \(\log\) 内实现链加,全局第 \(k\) 小,而这显然是无法实现的。

不过可以发现一件事情:每次加入或删除一个点后,对所有的点的 \(w_i\) 的修改量是 \(O(1)\) 的,那么容易猜到答案的变化量也是 \(O(1)\) 的,根据大样例容易确认这一点。记集合 \(C\) 的大小为 \(X\),不在集合内的最大值为 \(Y\),以插入为例子,考虑:

  • 插入一个点会使得若干个 \(w_i\) 加一,这最多使得 \(Y\)\(1\)
  • 这可能会使得非 \(C\) 集合内的最大值大于 \(C\) 集合中的最小值,不过容易发现这个最大值只有一个,于是只需要把这个最大值与 \(C\) 中的最小值交换一下,这不会使 \(X, Y\) 增加;
  • 插入的点 \(x\) 如果满足 \(w_x \ge Y\),那么把这个 \(x\) 加入 \(C\),这会使得 \(X\)\(1\)

以上分析说明插入一个数之后,\(X, Y\) 均最多增加 \(1\),说明答案最多增加 \(1\),删除的分析是同理的,那么说明我们并不需要每次三分找到最值点,仅需要在当前最值点进行 \(O(1)\) 的调整即可。同时,注意到插入删除点之后仅会使得当前的方案发生 \(O(1)\) 的修改,于是我们还可以直接维护出集合 \(C\)

我们可以钦定最后找到的方案中 \(X \ge Y\),因为如果 \(X < Y\),我们可以通过将 \(X\) 增加使得 \(X = Y\),这个过程中 \(Y\) 一定不会增加,也就是说,最后的答案仅跟 \(X\) 的值有关系。那么,现在的问题就是,插入一个数或删除一个数后,答案的变化。这是很容易的了,我们只需要让 \(X\) 进行 \(O(1)\) 的修改,然后看看 \(Y\) 的变化即可。由于我们已经维护出了 \(C\) 集合,那么我们所做的实际上是:将 \(C\) 集合中的最小值删去,或将非 \(C\) 集合中的最大值加入。

那么我们要维护的东西就简单多了:链加,找集合内的最大值或最小值。直接树剖线段树维护,即可做到 \(O(n \log^2 n)\)

考虑优化,我们发现我们仅需要找全局最大值最小值,那么我们可以不维护所有的 \(w_i\) 值,我们只关心其中的最值。注意最一开始的观察,我们可以发现,一条重链上的最大值一定是重链上深度最小的那个点,最小值一定是重链上深度最大的那个点,那么我们只需要维护这一个点就行了。但是我们还得维护所有重链中的最大值最小值,所以我们还得拿一个数据结构维护,支持插入删除,查询全局最值,有 \(O(n \log n)\) 次修改与 \(O(n)\) 次查询,发现如果用朴素数据结构维护还是两个 \(\log\) 的,使用 vEB 树可以做到 \(O(n \log n \log \log n)\),不过这太麻烦了,肯定不是我们想要的。

问题在于我们现在局限于维护出最值,而实际上这个信息也是不需要维护的,考虑到由于最值的变化量也是 \(O(1)\) 的,于是我们仅需要维护出每个值是否存在就可以了。具体而言,考虑以下算法:

  • \(X < Y\) 的时候:
    • 若存在点 \(x \not \in C\) 满足 \(w_x = Y\),那么我们将 \(x\) 加入 \(C\) 集合,\(X \gets X + 1\)
    • 否则,可以令 \(Y \gets Y - 1\)
  • \(X > Y\) 的时候:
    • 若存在点 \(x \in C\) 满足 \(w_x = Y\),那么我们可以将 \(x\) 移出 \(C\) 集合,\(X \gets X - 1\)
    • 否则,可以令 \(Y \gets Y + 1\)

那么现在问题就简单多了,我们对每个值维护一个链表,这样插入删除均可 \(O(1)\),于是本题就可以在 \(O(n \log n)\) 的复杂度内解决了。

需要注意的点:一个是插入删除之后选择方案会有 \(O(1)\) 的修改,一个是插入删除点后每条链的最深与最浅点可能改变,可以对每条重链用一个 set 维护,而改变之后需要知道新的 \(w_x\) 值,直接树状数组维护一下即可。

这 题 调 的 极 其 痛 苦。

CF487E Tourists

啥,我也不知道为啥要做这题。

首先发现一个点双内的所有点都是可以到达的,即对于任意三个点 \(u-v-w\) 一定存在一条不经过重复点的路径,于是建出圆方树,方点权值为相连的圆点的 \(\min\),答案即为路径最小值。

但是发现这样做不好修改,所以把方点改成只维护儿子的最小值,这样每个圆点唯一贡献一个方点,而这样最多仅有一个圆点不会被贡献,即路径 lca 的父亲节点,把这个点算上就行了。

前面的那个玩意的简单证明:考虑加一个点 \(p\),连边 \(u-p, w-p\),显然仍然点双联通,于是根据连通度的推论可以得到 \(p \to v\) 存在两条不相交的路径,把 \(p\) 点去掉就是一条 \(u-v-w\) 的路径。

2023.12.13

不知道为啥早上起床之后巨困。然后在机房趴了一早上,没去吃饭,事实证明不吃饭会饿。温馨提示:请善待小动物。

上午又下雪了,下雪还是很漂亮的,不过对于我这种文学荒漠来说也说不上来几个字,所以只能表述为雪花正在以与地平面相交且线面角约为 \(\frac{\pi}{4}\) 的直线以近匀速直线运动。原来物理也是荒漠啊,我还是不表述了。

貌似有人在传周六放假,声称“在级部电脑上看见的”,我寻思着级部也不会闲的没事在电脑上放上四个大字“周六放假”,还是说现在的学生已经可以为了得知啥时候放假去翻级部电脑了,这么牛的。17 号 THUPC,所以如果真放假我也就干脆不放了,哈哈。发现我没有任何 ACM 组队经验,做题策略是啥啊???似乎应当进行一些探讨。

哦又有人传有家长向教育部举报说放假太频繁了所以不放假了,哦这样啊,这下是彻底看不懂了,只能说大家的信息获取能力很厉害。

喷射火焰。

img

[WC2021] 括号路径

不难。

考虑如果 \(x \to y\) 合法,那 \(y \to x\) 也合法,而且在任意一个合法括号序列中间任意位置插入一个合法括号序列得到的括号序列一定仍然合法,也就是说如果 \(x-y\) 合法,那么我们就可以从 \(x\) 直接跳到 \(y\)。这实际上相当于每次找到一个合法括号子序列然后删去。而注意到任意一个括号序列都可以通过若干次删除一对匹配括号删光,那么我们就有了基本的想法:每次找到一对匹配括号(在图上即为一个点存在两个入边边权相等的点),然后这两个点直接可以互通,那么把这两个点直接合并起来。进行若干次操作后,最后答案就是每个被合并起来的点集选两个点的方案数。

维护做法就比较典了,首先上个并查集,然后入边可以直接 map 加启发式合并维护,合并的时候如果两个集合存在相同边权入边然后就合并,然后就做完了。代码异常简单。

CF1148G Gold Experience

当你想一个题想半天不会去看题解区发现之前想过的无敌暴力做法卡下空间能过时。

首先发现在这个图上做并不是很好做,那么我们求一个补图,问题就变成了找出一个大小为 \(k\) 的集合,使得这个集合要不然是一个独立集,要不然要这个集合每个点都有连边(即每个连通块大小至少为 \(2\))。可以证明,这个问题在任意图上都是成立的,我的证明及构造是:考虑将大小为 \(1\) 与大于 \(1\) 的连通块分开考虑,设大于 \(1\) 的连通块有 \(p\) 个,大小为 \(1\) 的连通块有 \(q\) 个,那么如果 \(q \ge k\),可以直接构造;否则,我们考虑构造后者,注意到选除了 \(q\) 个点之外的所有点一定是合法的,而由于 \(q < k\),则 \(n - q \ge k\) 是一定成立的,于是我们只需要找出一个恰好等于 \(k\) 的集合即可。考虑我们每个连通块中选 \(2\) 个节点可以做到 \(2p\) 个点,在 \([2p, n - q]\) 内的方案都是容易构造的,接下来我们找到一个连通块原来大小大于 \(2\) 的连通块(如果没有那么 \(n = 2p + q\),则一定有 \(p + q \ge k\),直接构造即可),然后每次删去一个大小为 \(2\) 的,并加上一个点,就可以构造出 \([2, 2p]\) 中的所有方案了。

那么现在问题在于如何找出这个补图,显然是不能建出来的,但是发现如果我们能找到一棵生成树就可以了,那么我们可以考虑模拟一下 dfs 或 bfs,也就是说我们需要每次在集合内找出一个与某一个数互质的一个数。

\(\omega(V)\)\(V\) 以内的数最多有多少质因子,这里 \(\omega(V) = 8\)

然后发现你做不动了。这 b 玩意也看着不咋会做,直接上个 bitset 吧,维护包含每个质因子的数的集合,然后把当前数的所有质因子的 bitset 全或起来取个反再和剩下的数与一下,复杂度 \(O\left(\frac{n^2 \omega(V)}{w}\right)\)这是我一开始想的无脑做法,Kubic 说能过。但是这玩意空间开不下,所以可以阈值分治一下,对于出现不到 \(O(\frac{n}{w})\) 次的质因子可以直接用 vector 存,其暴力操作的复杂度与 bitset 是一样的,而这样就只需要开 \(w\)bitset 了,于是空间复杂度就是 \(O(n(w + \omega(V)))\) 的了。

考虑个更优雅点的做法:我们发现如果 \(O(2^{\omega(V)})\) 容斥一下是可以求出一个点有多少出边的,于是我们可以二分找到一条出边,但是这复杂度显然是好似的。

考虑到问题在于我们的 dfs 每次需要删一个点重新二分,这样效率很低,所以我们把找连通块的生成树给弃掉。考虑贪心的选出一个极大的独立集,此时一定满足每个没选的点一定与一个选了的点相邻,那么我们可以找出所有没选的点与哪个选了的点相邻,这样我们使用类似的贪心算法仍然可以找出一个合法集合,而此时集合固定了,我们就可以二分一次得出答案了,整体二分即可,复杂度 \(O(n \log n 2^{\omega(V)} + n \sqrt{V})\)

正解是一个更复杂的构造,咕掉了。

P7039 [NWRRC2016] Integral Polygons

题单下一个题今天做不完了,为了防止看起来一天只做了两道题补一个水题。

考虑多边形面积等于每两个相邻的点之间叉积的和的绝对值除以二,这意味着是不是整数只跟叉积的奇偶性有关,而叉积等于 \(x_1 y_2 - x_2 y_1\),于是我们发现我么只关心 \(x, y\) 的奇偶性。

首先计算出整个多边形的大小,如果不是整数答案直接等于 \(0\)。下面就是考虑找出有多少多边形是整数,另一半就一定也是整数。考虑枚举两个断点 \(l, r\),两个多边形一定有一个在 \(l, r\) 内一个在 \(l, r\) 外,我们只考虑 \(l, r\) 内的,即 \([l, r]\) 形成的多边形,这等于 \(\sum_{i=l}^{r - 1} x_i y_{i+1} - x_{i+1} y_i + x_r y_l - x_l y_r\),记 \(p_i\)\([1, i)\)\(x_i y_{i+1} - x_{i + 1} y_i\) 的和,那么面积等于 \(p_r - p_l + x_r y_l - x_l y_r\)

同样的,我们只关心这几个数的奇偶性,那么我们可以直接 \(2^6\) 枚举这几个数等于什么,然后计数就很简单了,扫几遍就行了。

2023.12.14

活在梦里。


突然想起来好久之前看到过 这个游戏的宣传片,刚意识到这游戏已经发售好久了官方简中都出了好久了,看着好可爱想玩xwx


一上午没出机房,出门发现外面雪下的这么大了,上下一白,惟余莽莽。中午在路上走着,大雪纷飞,除了雪一直往我眼上扑让我睁不开眼以外,这场景真是极美的,很是欣喜吧。从楼梯处几扇关不上的窗户外飞进的雪,都已堆起了一座小丘,外面那个铁楼梯上有几阶的边角处堆积了有五六厘米厚的雪。有一种不真实的美感。总是有一种极大的把楼梯角上那一堆雪捧起来挥洒一地的欲望,却又不忍心破坏掉自然堆砌而成的处处可导的光滑曲线。给我减个十几岁我是不是就能在外面地上滚了。我小时候有这么幼稚么。

不过我下楼梯上厕所踩雪上滑倒的风险还是很大的,没准上面这段话是个遗言啥的。

P7531 [USACO21OPEN] Routing Schemes P

挺简单一题,昨天晚上实际上已经有做法了但是没写摆烂了。

首先考虑 \(k=0\),由于题目保证了有解,那么实际上我们只需要将每个节点的入度与出度配个对就行了,容易发现每一种配对一定都对应着一种解,由于这是个 DAG 所以不可能配出环,所以答案就是每个点的度数的阶乘的乘积(对于起始点与终点其度数为入度出度的最大值)。

考虑 \(k=1\),上述算法实际上会多算一些情况,发现这些情况实际上就是出现了环的情况,当一条反边与一条路径匹配成一个环时,这些路径是不会被经过的,所以这种情况是不合法的,我们需要把这些情况去掉。这是很容易的,设反边为 \(x \gets y\),我们可以直接 DP 求出 \(x \to y\) 的路径数。当然求路径数是不行的,因为实际的贡献不是路径数而是节点的阶乘,于是我们改成每个点的权值为 \(\frac{1}{deg_i}\),然后求出 \(x \to y\) 所有路径的权值即可。直接 DP 复杂度为 \(O(n^2)\)

考虑 \(k=2\),实际上也就是多了几种情况而已,设两条边分别为 \(p: a \gets b, q : c \gets d\),那么也就是下列几种情况:\(p\) 单独成环,\(q\) 单独成环,\(p,q\) 均单独成环,\(p,q\) 在同一个环。前两种情况会把第三种情况计重一次,所以第三种情况需要加上。现在问题在于如何求两条路径的方案数。

乍一看把两条路径的情况乘起来就行了,但是两条路径可能存在交叉的部分,而这些部分的贡献不再是 \(\frac{1}{deg_i^2}\) 而是 \(\frac{1}{deg_i(deg_i - 1)}\)。考虑改成一个二维 DP,求 \(f_{i, j}\) 表示当前第一条路径走到 \(i\),第二条路径走到 \(j\) 的权值数。为了防止同一对路径算重,我们每次让较小的一个点移动。这样的好处还有如果路径上出现了重合点,那么路径中一定存在一个 DP 状态的 \(i,j\) 相同,这样我们也容易将重合点的贡献给修正掉了。这样复杂度为 \(O(n^3)\),然后就做完了。

等等啥玩意,怎么有大力 BEST 定理做法啊呃呃。

啊,为啥这个做法没人写啊,这不是水笔做法吗。

P7246 手势密码

贪心哈哈哈。不会做,爆了。

首先我们肯定是在每个点决策有多少路径从当前这个点结合起来,然后再留一些链传上去,而结合路径是有经典的结论 \(c = \min\{sum - \max v_i, \lfloor\frac{sum}{2}\rfloor\}\),首先注意到,在当前子树内的贡献相同时,我们肯定会让传上去的路径尽可能多,这是显然的,而接下来我们就要考虑子树内贡献多少最合适。接下来进行一些分类讨论:

  • \(a_u \ge sum\) 的时候,这时候我们一定将所有的链全部传上去,因为此时无论如何都必须上传 \(a_u\) 条路径,这时候当然要让子树内的贡献减小;
  • \(a_u < sum\) 的时候,瓶颈在于 \(a_u\),此时一定是有一些链必须在内部消化的,那么我们就尽量全部内部消化掉,因为此时如果我们将一条链拆开,那么只有其中一端是可以往上传的,而往上传的这一条链最多只可能减少 \(1\) 的贡献,而此时内部已经多造成了 \(1\) 的贡献,于是往上传这时候就是不优的了,而当某一时刻 \(a_u \ge sum\) 时,再将所有的链全部上传。

但是这个贪心好像并不是很明晰,所以我们有更高级的做法:

考虑上述问题实际上是一个线性规划的问题,设 \(x_S\) 为路径 \(S\) 操作次数,那么限制条件就是 \(\sum_{S \ni u} x_S = a_u\),要求最小化 \(\sum x_S\)

写成线性规划问题的形式,就是:

\[\begin{array}{cl} \text{minimize } & \sum x_S \\ \text{s.t. } & \sum_{S \ni u} x_S \ge a_u \\ & \sum_{S \ni u} -x_S \ge -a_u \\ & x_S \ge 0 \end{array} \]

对偶一下,即为:

\[\begin{array}{cl} \text{maximize } & \sum a_u (s_u - t_u) \\ \text{s.t. } & \sum_{u \in S} s_u - t_u \le 1 \\ & s_u, t_u \ge 0 \end{array} \]

发现此时目标与限制都只与 \(s_u - t_u\) 有关系,我们记它为 \(b_u\),那么我们的限制就相当于:每条路径上的 \(b_u\) 和小于等于 \(1\),求 \(\sum a_u b_u\) 的最大值。

那么这个问题就简单多了,设 \(f_{u, d}\) 表示 \(u\) 子树内向上的链和最大为 \(d\)\(\sum a_u b_u\) 最大值,容易发现 \(d \in \{0, 1\}\),且 \(b_u \in \{-1, 0, 1\}\)。那么简单 \(O(n)\) DP 即可实现。

P9132 [USACO23FEB] Watching Cowflix P

我很恼啊,感觉我就一纯小丑!!!

怎么前面黑题会做,这边紫题都不会做。

上来我就想如何对每个连通块数求出点数最小值,然后拿 \(n\) 个一次函数维护个凸包,然后想半天没得做。

然后发现正解就是直接做。

考虑显然的 \(O(n)\) DP,于是可以 \(O(n^2)\) 了。再优一点,注意到连通块数小于 \(\frac{n}{k}\),因为每两个连通块之间距离至少为 \(k\),否则合并起来答案更优,那么考虑根号分治,\(k \le B\) 直接 DP,\(k > B\) 则连通块数小于等于 \(\frac{n}{B}\),对每个连通块数二分找到边界,做法与 CF1039D 一模一样。我是弱智。

CF1804H Code Lock

突然一下子好像想明白了,感动。 复杂度跑不动,不感动了。 看到 cf 提交记录有和我做法差不多的过了,又感动了。

首先发现我们可以记录下密码串都需要从哪转到哪多少次,然后密码串就没用了。

考虑一个无敌弱化版:如果只能顺时针转怎么做。发现很简单啊,拆个贡献,然后这玩意随便状压一下就做完了。

有顺时针有逆时针,很恼火。此时贡献大概是一个 \(\min\{|x - y|, k - |x - y|\}\),一定小于等于 \(\frac{n}{2}\)。考虑一个操作:我们将这个圆分作两半,将跨半圆与不跨半圆的分开考虑。发现,对于不跨半圆的部分,其距离一定小于等于 \(\frac{n}{2}\),而对于跨半圆的部分,我们可以将其中一个点旋转 \(\frac{n}{2}\),此时两个点就在同一个半圆内了,而且容易发现,两点之间的实际最短距离,就等于 \(\frac{n}{2}\) 减去旋转后的距离。于是乎我们就有了一个非常有趣的做法:先将集合分成两半,然后将其中一半旋转到另一半上,这样,所有的点对之间的距离就都只跟新的半圆上的点对之间的距离有关了,这样我们就可以很简单的用最一开始所说的做法去做了,这样时间复杂度 \(O(\binom{k}{\frac k2} 2^k k)\)。看着挺炸裂,但是卡卡常是能过的。

然而实现的时候发现如果按照同边正权不同边负权最后加上 \(\frac{k}{2}\) 乘上啥东西预处理权值会很烦人,所以可以把拆贡献改成同边如果不在一个集合则贡献,不同边如果在一个集合则贡献,把两个集合异或一下就全都是两个集合之间的点对贡献了,这是容易预处理出来的。

实际上应该是有更好的解释的,但是咕了,反正算法本质差不多。转的这个想法是从 AGC032F 来的。

2023.12.15

当大自然所留下的痕迹被悄然抹去,人们的记忆,是否能成为其曾存在过的证明?那份曾产生的欣喜,应贮存于何处?既然一切的终点必是消融,而曾逆风而行的你,能否将这段记忆,珍藏于心?

有感而发,与君共勉吧。不知道是太久没见过雪了,还是这次雪下的真的是很大,总之这次雪真的给我留下很深刻的印象,算是在自我封闭太久后,对自然美的感知与追求吧。

然后我又没看时间,早饭再度被我抛弃。挑战上午不被饿死。

完了不吃饭被我妈发现了 😕

CF986F Oppa Funcan Style Remastered

为什么我题单里面的紫题平均难度大于黑题。

容易发现 \(f\) 必须是一个置换,且满足每个置换环的长度不等于 \(1\) 且是 \(k\) 的因子,那么问题就变成了问 \(n\) 能否表示成若干个 \(k\) 的因子的加和。

那么肯定只用考虑 \(k\) 的质因子。我们可以分情况来讨论,如果 \(k\) 只有一个质因子直接判断就行了,\(k\) 有两个质因子那么就是在解一个二元不定方程,而 \(k\) 有至少三个质因子时,最小的质因子的数量级是 \(O(k^\frac 13)\) 的,于是可以直接跑同余最短路。分解质因子的时候直接分解比较炸裂,可以先把 \(\sqrt{k}\) 以内的质数全筛出来再分解,记不同的 \(k\) 个数为 \(w\),这样总复杂度 \(O(\sqrt{k} + \frac{w \sqrt{k}}{\ln k} + w \omega(k) k^\frac 13 + T \log n)\)

P5305 [GXOI/GZOI2019] 旧词

额,那啥,P4211 [LNOI2014] LCA

但是实际上两道题我都没写,好像是之间口胡 CF1098F 的时候看了这道题,因为这道题是那题的弱化版。OI 真有意思。

首先有朴素做法:考虑将 lca 的贡献拆分到 lca 到根上的每一个点上,这样查询时只需要查询点到根链上的所有贡献之和,因为任意两点的 lca 到根链等于两点到根链的交。然后就很简单了,树剖线段树,区间加 \(a_i\),区间求和,复杂度 \(O(n \log^2 n)\),上个 LCT 可以做到 \(O(n \log n)\)。注意这里的贡献可以是任意关于 lca 的函数,因为贡献只需要做一遍树上差分就可以计算出来了。

然后有一个比较神秘的做法,也是当时看 CF1098F 的时候看到的:两点之间的 lca 就是两点之间路径上深度最小的点,所以 lca 信息实际上也是路径信息,那么我们可以用点分树来维护这个信息。具体来说,考虑将每个分治中心到根经过的所有点标记为关键点,那么除了查询点所在子树以外的所有点,它们与查询点的 lca 就是它到分治中心的最近的一个关键点,这是极容易维护的,于是就做完了。同时,利用这个操作也可以将求和改成其它不能差分处理的操作,比如求最小值等,虽然这么整如果有区间查询操作或强制在线那么内层就必须再套个数据结构了。本题可以直接离线所以可以做到 \(O(n \log n)\) 的复杂度。

懒得写点分树了,摆了,其实不太难写。

P9341 [JOISC 2023 Day4] Security Guard

果然 joisc 的题还是重磅的。大概会个 76 分,写了 50 分,然后摆了看题解了。

感觉很牛啊!!!

题目的要求说的很诡异,不过大致整理一下发现实际上就是问每个船能否两边都停,过程显然可逆于是只要每个船都能通过一个流程到达对岸就一定可以到达所有位置。

一个一个子任务考虑,第一个子任务是链上且权值不超过 \(2\)。手模一下感觉应当是在连续的 \(1\) 段里面塞个 \(2\),这样从每个 \(1\) 段可以把多的一个保安挪到 \(1,2\) 的交接点,这样船上多一个保安变成两个保安,就可以到达 \(2\) 上了。

权值没有限制了,我们仍然沿用前面的直觉想法,感觉大概是在每个低谷放很多保安使得可以通过这些保安到达更高的位置。不过显然我们不能每个低谷都放等于最大值的保安数,注意到可以越过一个小峰到另一个低谷,这样实际上如果两个低谷能互相访问到那么这两个低谷就可以合并起来,如果需要更多保安只需要在其中一个谷里多加一些即可,不需要两个低谷都加。那么我们考虑从小到大枚举每一个点,然后考虑其左右两边,如果一边没有被选,说明当前点比那一边更小,那么我们让船停靠在当前点,答案加上 \(s_u\),如果一边已经被选过了,那么就可以将这两边合并起来,给这一边更多的保安使它能够到达当前点,答案加上的就是 \(s_u - s_{rt(v)}\),其中 \(rt(x)\) 表示 \(x\) 所在的并查集的根,也是最大值。那么我们通过这个过程一直合并,即可得到最少需要的保安数。

考虑拓展到树,发现这个过程直接拓展到树上显然不会有区别,所以直接整就行了。

拓展到图呢?既然已经有了树,不难想到最后肯定是保留一棵权值最小的生成树,那么我们就需要更仔细的推到出一棵树的贡献。上述答案分成了两种情况,我们合并在一起,让一开始停靠的贡献与合并放到一起去处理,也就是说改成一条边贡献 \(s_u + s_v - s_{rt(v)}\)。这个 \(s_{rt(v)}\) 看起来很难处理,但是注意到每个根都只会被合并一次,也就是说这个贡献实际上就是除了最大值以外的所有点的和,那么我们可以提前把这个贡献提出来,那么剩下的就只有边权是 \(s_u + s_v\) 了。那么问题就很显然了,只需要找出来一棵最小生成树即可。

那么题目要求的问题就更清晰了:实际上就是问加 \(k\) 条边后的最小生成树的边权和的最小值。

考虑加边最小生成树怎么做,我们每次加入一条边后,应当是删除这条边所在路径上的最大值。反过来考虑,对于一条边,如果它被删了,那么加的边应当是两个连通块之间的最小边,而由于边权等于两端点权之和,于是就相当于在两个连通块中分别找到最小点权的点然后连边。注意到这覆盖了所有点,于是这两个点中一定有一个点是全局最小值,也就是说加的边仅有 \(n-1\) 种,那么此时就有暴力的 \(O(2^n)\) 的做法了。而可以发现,\(k\) 的答案集合应当是包含于 \(k+1\) 的答案集合的,于是我们每次找到贡献最大的一条边然后把这条边加入即可做到 \(O(n^2)\)

现在问题在于快速找到这条贡献最大的边,但是发现这是很困难的,因为树的形态一直在变,你要做这个玩意起手得上个 LCT,而且边权还跟子树有关系,反正不太能做。但是注意到我们知道最终的树长啥样,一定是最小的点连出的一个菊花图,于是考虑倒着去做。那么我们每次就是删去根与某个点之间的边,然后加上原图中某一条边,将割下的一个连通块与某个连通块合并起来。由于我们每一时刻要保证它都是最小生成树,所以我们连的边减去删的边一定要是最小值。而这个就容易维护多了,我们只需要维护出每个连通块中往外连的边的最小值,然后再用一个数据结构维护出每个连通块的最小权值,每次取所有连通块中的最小权值即可。注意到有合并操作,我们可以使用可并堆维护,复杂度 \(O(n \log n)\)

2023.12.16

P6277 [USACO20OPEN] Circus P

被彻底击溃了。想了好久才大概能把整个过程想明白,大概写一下吧。

首先容易发现每种等价类的大小一定是相等的,那么最后答案就一定是 \(\frac{k!}{size}\)\(size\) 表示等价类的大小。

我们考虑两头牛之间能不能交换,发现这种关系是具有传递性的,那么交换的关系图一定会形成若干个团,一个团之间是可以任意交换的。那么最后答案就是 \(\frac{k!}{\prod s_i!}\),其中 \(s_i\) 表示每个团的大小。

我们考虑什么时候能交换,容易想到一种简单的交换就是在一个度数为三的点上去换。这是一个最基本的交换,只要在任意一个度数大于等于 \(3\) 且存在至少两个空位的情况下就可以在这里进行交换。

而整棵树由若干关键点划分成了若干条链,我们考虑一条由两个关键点连起来的一条链上能否进行交换。我们设两端两个关键点的子树大小分别为 \(a, b\),中间的链长为 \(c\),其中两个端点同时包含在链与子树内,显然有 \((a - 1) + (b - 1) + c = n\)

首先考虑到我们的问题是能够使得两头牛交换且不改变其它牛的位置,但是不改变其它牛的位置这个限制实际上是没有意义的,因为如果某一刻能够使得两头牛交换位置,那么我们再按照一模一样的移动倒着就能把其它所有牛全部还原回去,所以我们实际上只用考虑某一刻能否将考虑的两头牛交换即可。

那么我们考虑链上两头相邻的牛能否交换。直观的想法肯定是让这两个点左边的牛全部往左边的子树里填,右边的牛全部往右边的子树里填,这样这两头牛可以去任意一个关键点的地方去调换位置。而子树内可以包含的牛是有限的,注意到如果左右两棵子树全都填满了,那么此时这两头牛就不能交换了,这时一定有 \(k \ge (a - 1) + (b - 1)\),而由此我们又可以推出,存在恰好 \(k - (a - 1) - (b - 1)\) 头牛之间是不能交换位置的,而这会导致此时左边的任何点都无法与右边的任何点进行交。

如果 \(k < (a - 1) + (b - 1)\),那么说明将所有的牛全部填入子树后,一定存在一棵子树里至少有一个空。考虑链上相邻的两头牛,如果将这两头牛的左边所有牛全部填入左子树,右边所有牛全部填入右子树,那么可以发现存在一个子树至少有两个空,而通过这两个空我们就可以完成这两个点的交换了,也就是说,当 \(k < (a - 1) + (b - 1)\) 时,我们可以交换链上任意两头牛的位置。

总结一下,对于一条链,如果 \(k \ge (a - 1) + (b - 1)\),那么存在一些牛不能互相交换,同时这些牛将左右两个集合分开,使得左右两个集合之间也不能交换。这在交换的关系图上意味着,左右两个集合不可能在同一个团内。而 \(k < (a - 1) + (b - 1)\) 时,链上的任意两个点之间都是可以交换的,这说明只要能移动到这条链上的点都是可互相交换的,这在交换的关系图上意味着,左右两个集合是在同一个团内的

这时候问题就清晰了许多了。我们称满足 \(k < (a - 1) + (b - 1)\) 的链为合法链,那么整个交换关系图,是由若干条不合法链划分成的若干个团,而合法链将关键点连成了若干个连通块,每个连通块对应着一个团。那么,我们就只需要求出每个团的大小,就能计算出答案了。

首先链合法的条件可以改写成 \(k < n - c\),即 \(n - k > c\),那么发现如果我们将 \(k\) 从大往小枚举,则会依次加入每一条合法链,然后使用并查集即可维护关键点形成的连通块。那么现在问题在于,如何计算出每个连通块对应的团的大小。

团的大小并不好计算,但是我们是容易计算团外面的点数的,于是简单容斥一步就可以了。对于团外面的点数,我们考虑连通块往外连的每一条不合法链,设连通块外的子树为 \(a\),连通块内的子树为 \(b\),那么我们让所有的牛全部填满子树 \(b\),则多出来的点就是这条不合法链方向的不在连通块内的点,这些点的数量等于 \(k - (b - 1) = k - (n - c - (a - 1)) = k - n + c + a - 1 = k - n + 1 + p\),其中 \(p\) 为不合法链方向的关键点子树大小。将所有的出边求和,容易发现,\(\sum p = n - x\),其中 \(x\) 为关键点连成的连通块点数,于是总和就是 \(n - x + y(k - n + 1)\),拿 \(k\) 减去它就是团的点数,即 \((y - 1)(n - k - 1) + x - 1\)。但是这个式子里面跟 \(k\) 有关系,也就是每个连通块的权值一直在变,这导致我们并不能 \(O(1)\) 维护出所有连通块的权值乘积了。但是实际上我们可以直接枚举每一个连通块进行计算答案,因为连通块数量等于不合法链的数量加一,而不合法链存在的时间等于链长,链长总和是 \(O(n)\) 的,所以每一时刻下连通块数量的总和也是 \(O(n)\) 的。于是我们可以拿链表或者 set 维护所有的连通块,每次遍历所有连通块计算答案即可。时间复杂度 \(O(n \log n)\)\(O(n \alpha(n))\)

P9020 [USACO23JAN] Mana Collection P

这题还是很有意思的。

首先肯定我们仅需要考虑最后一次经过每个点,不需要每次碰到都收集一次,那么我们就可以考虑一个状压 DP:\(f_{S, i}\) 表示当前经过了 \(S\) 这些点,最后一个点在 \(i\) 处。但是发现贡献很诡异,并不好转移。

考虑拆贡献,设依次经过的点是 \(c_1, c_2, \cdots, c_k\),最后一次到达的时间分别是 \(d_1, d_2, \cdots, d_k = e\),那么最后的答案就是 \(\sum m_i d_i\),而显然我们要让 \(d_i\) 尽可能大,于是全部取到最大值 \(d_i = s - \sum_{j=i}^{k - 1} dis(c_j, c_{j+1})\)。那么答案就是:

\[\begin{aligned} &\sum_{i=1}^k m_i \left(s - \sum_{j=i}^{k - 1} dis(c_j, c_{j+1})\right)\\ =&s \sum_{i=1}^k m_i - \sum_{i=1}^k m_i \sum_{j=i}^{k - 1} dis(c_j, c_{j+1})\\ =&s \sum_{i=1}^k m_i - \sum_{j=1}^{k - 1} dis(c_j, c_{j+1}) \sum_{i=1}^j m_i \\ \end{aligned} \]

发现这是一个关于 \(s\) 的一次函数。如果最后的集合是固定的,那么斜率是固定的,为了答案最大,我们只需要最小化截距,而后面那个东西是容易状压 DP 的,于是直接跑一边状压 DP 即可计算出每个集合的最小截距。然后问题就是一个一堆一次函数里面求极值的问题,这个单调栈或者李超树随便搞一下就行了。时间复杂度大概一个 \(O(n^3 + 2^n n^2 + q)\) 的东西。

2023.12.17

马上 THUPC 了!

做一道题然后摆了。等比赛开始。

upd. THUPC 似了,143 名。我懒得写游记了,看 耳朵龙写的吧

CF1799F Halve or Subtract

妈的早上打个题,感觉跟个弱智一样,希望我 THUPC 脑子没这么炸裂。

发现数无非就四种状态:不操作,操作 1,操作 2,操作 1+2。两个操作同时做肯定先做第一个再做第二个。

然后考虑枚举操作 1+2 的数量,这样所有操作的数量就都固定了。考虑一下顺序怎么放。

无非就是考虑几种大小关系(不操作肯定是最小的,不用考虑)。

假设两个数是 \(x,y\)

  • 操作 1 v.s. 操作 2
    即比较 \(\max(y - b, 0) + \lceil\frac{x}{2}\rceil\)\(\max(x - b, 0) + \lceil\frac{y}{2}\rceil\) 的大小关系。为了拆开 \(\max\),我们分两种情况考虑:
    • \(x, y \ge b\):那么有 \((y - b + \lceil\frac{x}{2}\rceil) - (x - b + \lceil\frac{y}{2}\rceil) = (y-x) - (\lceil\frac{y}{2}\rceil - \lceil\frac{x}{2}\rceil) \ge 0\),即后一种情况更小,也就是说如果两数均大于等于 \(b\) 那么操作 1 在后操作 2 在前;
    • \(x, y < b\):那么有 \(\lceil\frac{x}{2}\rceil - \lceil\frac{y}{2}\rceil \le 0\),即前一种情况更小,也就是说如果两数均小于 \(b\) 那么操作 2 在后操作 1 在前;
  • 操作 1 / 操作 2 v.s. 操作 1+2:
    具体过程略去,容易得到操作 1,2 在前,操作 1+2 在后。

根据上面的讨论,我们就可以得知,最后的选择一定是有一段都不操作,有一段操作 1,有一段操作 2,有一段操作 1,再有一段全操作。那么我们前缀和处理一下,然后再枚举操作 2 的一段的位置,即可计算答案。复杂度 \(O(n^2)\)

还可以发现答案关于操作 1+2 的个数是单峰的,于是外层套个三分可以做到 \(O(n \log n)\)

还有很多种做法,例如上述做法在枚举完之后还可以直接反悔贪心,等等,不过多叙述。

THUPC 2024 初赛 套娃

挑个我觉得不错的题写写吧。这题是我场上过的(叉腰)

据说跟 Holy Diver 很像,我没做那题,不过这题还是想出来了。

首先发现我们只关心每种区间 mex 出没出现过,如果我们能找出一种 mex 出现的长度区间,我们就可以扫描线求出每种长度的答案了。

考虑 \(\mathrm{mex} = k\) 的条件,首先是区间内需要出现 \([0, k-1]\) 中的所有数,二是不能出现 \(k\)。那么我们可以按照 \(k\) 的出现位置将整个区间划分开,那么对于每一个区间内,如果我们能找到区间内的最短的满足第一个条件的区间长度 \(mn\),那么对于 \([mn, r - l + 1]\) 内的所有长度就都存在 \(\mathrm{mex} = k\)。而区间数与出现次数同阶,出现次数总数 \(O(n)\),所以这样的区间也只有 \(O(n)\) 个。

那么问题在于怎么求区间内最短的包含 \([0, k - 1]\) 的区间。我们有一个朴素的双指针的求法,即对于每个右端点求出其最靠右的左端点,这个是好求的。我们考虑能不能通过维护这个东西来快速求,即维护一个数组 \(f_r\) 表示以 \(r\) 为右端点的最短的合法区间的左端点,容易发现这个数组单调。直接求单个 \(f_r\) 是不好求的,但是我们发现当 \(k\) 变成 \(k+1\) 的时候,这个变化是容易求的,因为此时每个区间多了一个必须包含 \(k\) 的限制,那么只需要让所有 \(f_r\)\(r\) 左边第一个出现的 \(k\) 的位置取 \(\max\) 即可,而又因为它有单调性,所以可以二分找到这个位置变成区间赋值。还需要求 \(r - f_r\) 的最小值,可以直接线段树区间赋等差数列,但是这样太麻烦了,我们可以使用珂朵莉树来维护,因为一段 \(f_r\) 相等的区间肯定左端点的值最小,那么我们再加棵线段树维护区间内左端点的值即可。这样就可以 \(O(n \log n)\) 解决这个题。

THUPC 2024 初赛 一棵树

具体做法就不详细写了,反正你发现这玩意与 Factories Once More 的式子是几乎一模一样的,而这题贡献函数还简单,其差分是两段相等的值。

可以直接上 Splay 启发式合并做到 \(O(n \log n)\),也有常数更小的做法:发现这里的操作永远都是对固定的一端区间进行区间加,那么我们可以直接用个对顶堆来维护这个东西,这样常数就很小了。用个可并堆可以直接合并,但是我没想明白用可并堆怎么去对顶堆调整,摆了,反正堆启发式合并也比平衡树快。

妈的我 Treap 启发式合并被卡常了,气死我了。

2023.12.18

上午打了 USACO 23DEC Pt 组,口胡了一会然后感觉代码太难写就咕了,所以其实我也不知道我做法对不对,但是现在比赛还没结束所以我也不好在这里写题解,现在我也交不了代码测,所以摆了。

实际上题解写了,就在下面,但是我给注释掉了,比赛结束再放。

而且这博内容挺多了,再写就开新博了。哈哈。

P9983 [USACO23DEC] Cowntact Tracing P

如果全是 1,我们有一个直观上的贪心,就是树上 dfs 能不放就不放,但是具体来说情况比较复杂,因为子树也可以向子树贡献。于是我们考虑这样一个过程:给每个点定两种状态的一种,一种是子树内所有点全部被覆盖,一种是子树内存在点没有被覆盖。前者我们只关心子树内放的最浅的一个点,后者我们只关心子树内没被覆盖到的最深的一个点。然后 dfs 合并一下,设前者最浅的点的深度为 \(f_u\),后者最深的点的深度为 \(g_u\),同类型合并直接合并,不同类型则看能否将未覆盖的点覆盖,那么大致有如下合并:

void dfs(int u, int pre) {
    f[u] = 0, t[u] = 1;
    for (int v : e[u]) if (v != pre) {
        dfs(v, u);
        f[v]++;
        if (t[u] == 0 && t[v] == 0) f[u] = min(f[u], f[v]);
        if (t[u] == 1 && t[v] == 1) f[u] = max(f[u], f[v]);
        if (t[u] == 0 && t[v] == 1) {
            if (f[u] + f[v] > k) t[u] = 1, f[u] = f[v];
        }
        if (t[u] == 1 && t[v] == 0) {
            if (f[u] + f[v] <= k) t[u] = 0, f[u] = f[v];
        }
    }
    if (t[u] == 1 && f[u] == k) ans++, f[u] = 0, t[u] = 0;
}

考虑不全是 1 怎么做,首先先考虑 0 怎么处理,实际上 0 就是限制了一些点是不能选的,可以先整体 bfs 一边找出每个点到最近的 0 的距离,然后就可以简单判断出这个点能不能选了。然后就是上一个 DP,直接把上面的 f,g 压成状态即可。注意需要对每个 1 的连通块单独 DP,那么大概有:

\[\begin{aligned} f'_{u, x} + f_{v, y} & \to f_{u, \min(x, y)}\\ g'_{u, x} + g_{v, y} & \to g_{u, \max(x, y)}\\ f'_{u, x} + g_{v, y} & \to \begin{cases} f_{u, x} & x + y \le k\\ g_{u, y} & x + y > k\\ \end{cases} g'_{u, x} + f_{v, y} & \to \begin{cases} f_{u, y} & x + y \le k\\ g_{u, x} & x + y > k\\ \end{cases} \end{aligned} \]

很容易发现这是个只跟深度有关系的 DP,所以容易考虑到去长剖优化。但是这里对于深度大于轻儿子深度的 DP 值处理比较麻烦,不过分析上面式子之后发现对于深度大于轻儿子深度的值的贡献一定都是原值加一个固定常数的形式,那么我们可以先把所有的这样的取 \(\min\) 操作拿出来,然后经过一些处理,可以把贡献变成区间加的形式,用线段树维护一下 DP 值即可。复杂度 \(O(nq \log n)\)

感觉这玩意写起来太麻烦了所以就摆烂了()感觉像有简单贪心做法的样子,但是好像能构造出任意不能被选择的点集?看着又不像除 DP 外能做的东西了。

P9984 [USACO23DEC] A Graph Problem P

首先发现这算法流程写的不就是 Prim 吗,于是可以发现最后操作的一定是最小生成树。

然后再考虑这玩意咋做。先考虑链,一个朴素的想法就是猜测能到达的区间合法状态很少,然后记搜,但是发现是可以卡到 \(O(n^2)\) 的。

那么这种时候肯定去考虑加一些限制使得合法区间减小。链上最直接的考虑就是仅考虑改变方向时的状态,也就是说向左一直选到边比右边大为止,反之亦然。那么发现,此时左端点仅有可能在右端点左边第一个比它大的数停,右端点尽可能在左端点右边第一个比它大的数停,那么发现这样合法的状态就仅有 \(O(n)\) 种了,而跳的过程的答案简单前缀和一下就很容易能维护出来了,这样直接上个记忆化搜索就行了。

放到树上呢?我们同样可以考虑类似的做法,考虑当前的连通块变化。考虑当前出边中最小的边是 \(a\),次小的边是 \(b\),那么发现我们一定是先选 \(a\) 子树,然后一直在选 \(a\) 子树内的边权小于 \(b\) 的所有边,直到没有了之后去选 \(b\)。那么发现,此时的连通块,实际上就是仅保留 \(<b\) 边权后,\(b\) 这条边两个端点所在的两个连通块,也就是说这样合法的连通块数同样是 \(O(n)\) 的,这与链的情况是相同的。但是,链可以很容易的计算出一段区间上选的权值,而树上就很困难了,而且看起来这是一个递归问题,\(n\) 叉递归下去肯定是不会有更优秀的结果的。

那么考虑用更优秀的方法去刻画这个问题。仔细思考会发现,这些连通块实际上可以形成一棵树的结构,其实就是 Kruskal 重构树。那么此时我们的问题就明晰许多了,我们做的事情实际上就是,从 Kruskal 重构树的某一个节点开始往根跳,每跳一次之后将其兄弟中的所有边全部加入,然后加上了一些权值。发现,这实际上是给每个点和每条边赋了权值,一个点的贡献就是点到根的权值和。那么我们只需要处理出边权即可。而注意到这个形式好维护多了,而我们刚才的问题在于需要递归求解,此时递归求解就容易多了,我们要做的就是 dfs 一遍求出所有边权,边权就是在另一棵子树内某一个点跳到当前点的贡献,这拿线段树很容易维护。

最后就可以 \(O(n \log n)\) 解决这个问题了。

上午中途基本开始水 B 站了,大概十一点多才继续想这个题,11:40 左右想到了怎么做,然后继续摆去了()

P9985 [USACO23DEC] Train Scheduling P

fzj 说他就过了这题,但是我场上根本没看这题,不知道是不是弱智题,反正我感觉上大概是这么做:

肯定是考虑把 A, B 两种起点的分开来,然后最后一定是一段 A 一段 B 然后交替。那么考虑一个 DP \(f_{i,j,t}\) 表示目前已经考虑了前 \(i\) 个 A,前 \(j\) 个 B,最后一趟车的时间是 \(t\),延误时间最小是多少。你肯定不能这么记啊,部分分都一分拿不到的,注意到所有时间肯定越靠前越好,所以如果你知道最后一次时间没变的车,那么你只需要记录这趟车和之后 AB 交错了多少次,那么你就可以推算出最后一次车的时间,那你喜提一个 \(O(n^4)\) 做法。

然后你发现一件很有趣的事情:如果你记录了最后一次时间没变的车和交错次数,那么你可以直接推算出考虑了前几个 A 与前几个 B,因为你知道这一段的最终形态,那么在这一段内的所有 AB 都一定是移动到离最终形态中最近的一个,这些一定都是确定了的,所以我们只用考虑下一步是继续交错,还是选择下一个 A 或 B 作为最后一次时间不变的车。那么你只剩下两维状态了,但是你得枚举下一个 A 或 B 是啥,因为这中间的所有 B 都得到下一个 A 或 B 的后面,所以是个 \(O(n^3)\)。然后再前缀和优化下就可以 \(O(n^2)\) 了。(大概)

感觉上过程还是每次确定一整段 A 或 B,可以发现可能的到站时间只有 \(O(n^2)\) 种,而每确定一次车之后,会有许多车与之冲突,需要决策这些车到哪里,于是需要考虑当前这段的结尾位置,然后将所有冲突的车全部挪到结尾后面,然后再将与这些结尾冲突的车继续往后挪,可以继续确定这段车的结尾。

posted @ 2023-12-11 20:54  APJifengc  阅读(541)  评论(11编辑  收藏  举报