做题记录 1

前言

本赛季我的战绩极为招笑,真正意义上。

CSP T2 严重饭堂,然后被 windows 偷袭,怒挂 40peters 之后成功被几乎所有人击败,NOIP 冲一整场 T2 获得 28peters,联合省选莫名其妙读错 D1T1 4h,D2 分数带小数点。

联合省选出分的时候我盯着那极为招笑的成绩不知道该干啥,数次萌生退役的想法。那之后很长一段时间,我每天都在机房看别人的游记、回忆录,并且逐渐魔怔。然后是知道了能去 APIO,考完的时候觉得没有什么遗憾,已经尽力了,结果回头发现我确实是个唐诗。果不其然是 Fe,但是以这种方式 Fe 确实是招笑。

后来好像就是浑浑噩噩的,与同届甚至低一届的人差距越来越远。现在每天就做一点点 CF 的题目,而且难度不高,被很多人吊起来打。不过我倒是开始喜欢写一些做题笔记了,于是就开始写这篇文章了。

如果您觉得我写的文字太过招笑,望见恕。

7 月 18 日

1. CF1840G2 *2500 好玩的交互题。

\(n \le 10^6\),询问次数 \(q \le 10^3\)

首先考虑 G1。2023 次询问的限制比较诡异,猜测是 \(2\sqrt n\) 级别的。这样就比较显然了,首先跳出连续的一段,然后后面继续大块大块地跳,跳到记录的那一段上就是可以的。这样,设第一步跳出 \(B\) 个部分,那么后面由于需要保证跳回这个段的时候恰好过了一圈,只能跳 \(B\) 步。于是就是 \(B + \frac{n}{B}\) 级别的。取 \(B = \sqrt n\) 就可以通过。

接下来,感觉应该没有什么更优秀的做法。因为如果用到约数等乱七八糟的东西,无法支持一些情况(亲测)。于是考虑优化 G1 的做法。

优化无非就是缩小 \(n\) 的范围。注意到,一次询问可以得到另一种信息,就是 \(n \ge pos\)\(pos\) 是返回值。

于是考虑先通过若干次询问确定 \(n \ge k\),随后再询问处连续一段(段长为 \(B\)),加一个询问 \(k - B\)(如果 \(k\) 更小就不加这个操作),接下来是每次询问 \(B\),这样花费次数为 \(B + 1 + \frac{n-k+1}{B}\)。如果希望继续控制在 \(2B + 1\) 次以内,那么 \(k \ge n - B^2 + 1\)

由于序列可以认为设置,所以考虑第一步随机化。

假设最初随机询问 \(d\) 次,那么错误概率为 \((\frac{n - B^2}{n})^d\)。取 \(d = B = 333\),总询问次数为 \(d + 2B + 1 = 1000\),同时错误概率约为 \(9 \times 10^{-18}\)。可以通过。时间复杂度 \(O(q)\),空间复杂度 \(O(n)\)

2. CF1842E *2300 比较板子的线段树优化 dp。

由于 \(0\) 看起来不太好,所以首先将 \(x,y\) 都增加 \(1\)\(k\) 增加 \(2\)

然后呢有一个小结论,就是三角形不会相交,不然可以用一个大三角形覆盖。

考虑设 \(dp_i\) 表示消除到了第 \(i\) 列,最少花费多少代价。

设此时不能被这一个大三角形消除的(也就是 \(y\) 坐标小于当前的)单消代价前缀和为 \(sum\) 数组,那么可以写出式子 \(dp_i = \min \{dp_j + sum_i - sum_j + (i - j - 1) \times A \}\)。这个东西可以用线段树维护。\(0 \le j < i\)\(j = 0\) 特殊处理即可。时间复杂度 \(O(n \log n)\),空间复杂度 \(O(n)\)

7 月 21 日

3. CF2073H(P15499) *2700 提供了一种非常具有启发意义的特殊二分形式。

首先考虑手玩一下,发现知道相邻两个的 multi,如果有一个不是 \(0\) 就可以求出当前的 \(l,r\) 值,又手玩一下发现 \(l_i - r_i\) 每次 \(+1\)。但是可能左边有一堆 \(0\),右边也有一堆 \(0\),这就比较难以进行朴素的二分。如果非要进行朴素二分的话询问次数是 \(2 \log n + 1\),约为 \(15\) 次,无法通过。笔者到这里就思考不下去了。

不过注意到,我们要找的其实无非是玫瑰-百合这么一组东西,如果出现了这个东西,就一定可以计算出答案(具体见下文)。于是考虑一种特殊的二分

具体地,我们钦定 \(0\) 为玫瑰,\(n+1\) 为百合。每次二分询问类型,如果是玫瑰,就钦定 \(l = mid\),否则钦定 \(r = mid\),这样一定能够找到一组解。

考虑对解进行分类讨论。

  1. \(l = 0\),那么询问 \(1\) 就可以得到 rose 的数量。然后直接输出这个值就可以了,因为我们得到了 \(l_0 = 0, r_0 = k\)。根据上面一个加粗部分就有这个。
  2. \(l = n\),询问 \(n - 1\) 可以得到 lily 的数量 \(k\),输出 \(n - k\)
  3. \(1 \le l < n\),这种情况下,我们不考虑询问 \(l,r\),因为如果这样的话都是 \(0\) 是比较难办的。但是,询问 \(l - 1,r\) 就可以规避这一点。不难发现,设 \(l - 1,r\)\(l,r\) 值分别为 \(x_1,y_1,x_2,y_2\),有 \(x_2 = x_1 + 1, y_2 = y_1 - 1\),那么知道了 \(x_1y_1,(x_1+1)(y_1-1)=x_1y_1-x_1+y_1-1\) 的值,作差就可以求出 \(x_1 - y_1\) 了!而我们的最终目的,也是求出这个东西。最终的答案容易发现就是 \(l - 1 + y_1 - x_1\)

4. CF425C *2300 比较简单的 dp 题。

首先注意到 \(e\) 的数据范围限制比较诡异,似乎是说明最多只能删 \(\frac{s}{e}\) 次,而且这玩意比较小。

开始有一个比较朴素的思路就是设 \(dp_{i,j}\) 表示 \(a\) 的前 \(i\) 个与 \(b\) 的前 \(j\) 个。这个显然是会爆掉的。那么考虑与 \(\frac{s}{e}\) 结合一下,常规地,设 \(dp_{i,j}\) 表示只使用 \(a\)\(1 \sim i\),使用 \(j\) 次操作 \(1\)\(b\) 最小被消到哪里。这样转移显然有 \(dp_{i,j}=dp_{i-1,j}\),然后考虑用 \(i\) 去结合。那么 \(b\) 中被匹配的元素一定要等于这个值,而坐标又要大于 \(dp_{i-1,j-1}\)(显然)。于是对于 \(b\) 中每一个元素扔进存值域的 vector 里面就可以了。

\(\frac{s}{t}=m\),则时间复杂度为 \(O(nm\log n)\),空间复杂度为 \(O(nm)\),还是挺简单的。

5. CF1842F *2500 需要运用到重心的关键性质。

考虑固定根节点 \(rt\),设 \(s_i\)\(i\) 为根的子树的黑点个数,那么答案就是 \(\displaystyle \sum_{i \neq rt} \mid k-2s_i \mid\)

对于含有绝对值的式子,直接计算显然是困难的。于是我们考虑把这玩意绝对值扔掉。注意,如果钦定 \(rt\) 就是新树的重心,那么一定有 \(k \ge 2s_i\)。这样就可以消掉绝对值了!这便是一个经典 trick:遇到绝对值尽量消掉。注意到如果枚举到其他的根节点的时候,答案一定是变小而非变大,又每棵树都有重心,因此显然保证了答案的准确性。

拆一拆贡献,发现 \(i\) 染成黑色只会对它和它的祖先(不包含根)产生贡献。这个贡献可以计算,就是 \(dep_i - 1\)。因此,答案变成 \(k(n-1)-\displaystyle \sum_{i \neq rt}(dep_i-1)\)。注意到,dep 越小越好。于是考虑对每个点,以此为根(重心)进行 bfs 计算答案。时间复杂度 \(O(n^2)\),空间复杂度 \(O(n)\)

7 月 22 日

6. CF1843F2 *2300 比较典的题目。

发现一个序列,左右拓展之后值最多改变 \(1\),因此不难发现路径内所有子段值域是连续的。所以问题转化为求树上最大最小子段和。这个在线段树上是容易维护的,于是考虑树剖然后把 \(\log n\) 级别的区间再扔出来 push_up 一下就可以了,时间复杂度 \(O(n \log^2 n)\),空间复杂度 \(O(n \log n)\),然而代码勾石。

考虑直接用求 LCA 的方式,这样也是划分成 \(\log n\) 级别的段,push_up 一遍就可以了。这样代码量很小,不过空间带点常数。

本人采取了第一种做法,鉴定为 m。

7 月 23 日

7. CF1063D *2600 这是一道数学题。

注意:最后一个是 \(2\),即使此时只剩下一个糖果也是没问题的。

\(t\)\(l \sim r\) 的人数。注意分类讨论一下。然后验证一下全是 \(1\) 可不可以,这样可以保证之后验证的答案都需要满足 \(\ge 1\)

\(p\)\(2\) 是符合条件的。则可以列出这个柿子:\(t + \max(0,p+t-n-1)\le k \bmod (n + p) \le 2t\)。不过容易发现,\(t = n\) 的时候这个柿子是不对的。这个时候,有两种情况,一种是刚好全部消除,也就是 \(k \bmod (n + p) = 0\),这个枚举即可,是 \(\sqrt k\) 的复杂度。另一种,就是最终有剩余。不过由于前面 \(n - 1\) 个不能消完,所以需要满足 \(n - 1 + p - 1 < n\),于是 \(p < 2\),只需要验证 \(p = 1\) 是否可行。于是只需要 \(k \bmod (n + 1) = n\),那么 \(p = 1\) 就是可以的。二者取个 max 就可以了。

对于 \(t \neq n\),那么就需要用到上面这个不等式了。感觉 mod 操作比较不好处理,于是考虑整除分块,考虑枚举 \(u = \lfloor \frac{k}{n+p} \rfloor\),则设枚举 \(u\)\(a \le n + p \le b\),移项有如下不等式组:

\[\left\{ \begin{array}{} a - n \le p \le b - n\\ k-nu-pu\le2t\\ k-nu-pu\ge t\\ k-nu-pu\ge t+p+t-n-1 \end{array} \right. \]

整理一下得到:

\[\left\{ \begin{array}{} p \le b - n\\ p \ge a - n\\ p \ge \lceil \frac{k-nu-2t}{u} \rceil \\ p \le \lfloor \frac{k-nu-t}{u} \rfloor \\ p \le \lfloor \frac{k-nu-2t+n+1}{u+1} \rfloor \end{array} \right. \]

这样就可以求出当前条件下 \(p\) 的最大值,最后整理一下就可以了。

8. CF812D *2700 这题纯属文字游戏。有一句话没读到,我已经急哭了。

首先发现记录每本书的 \(lst_x\),然后每次更新,可以计算出一个人依赖的人。然后又说不会有人哭泣,那就是不会形成环。不过这个状物仍然是不好处理的。这个时候我们需要祭出题面中的文字:

要是有孩子没获得请求的玩具,他将一直等待,不能在此期间请求其他玩具。
且还没有人完成玩耍。

哦对的,于是就说明,在请求过程中一个人最多只会有一个依赖,于是从它的依赖人向它连边。这样,每个人入度最多为 \(1\),于是构成一个森林。

考虑一次查询操作 \((x,y)\),如果 \(y\) 这个玩具还没有人请求过,显然是 \(0\)。否则找到这个 \(lst_y\)。如果这个东西在 \(x\) 的子树内,显然 \(x\) 无法获得,它的子树内其他人也无法获得。于是答案为 \(size_x\)。否则容易发现一定可以先将其他的都玩完,然后 \(x\) 为根的子树再进行玩耍,这样是可以的,答案为 \(0\)

这题如果题面没写这么复杂应当是不可能有 *2700 的。洛谷上降绿了确实是没啥问题。

7 月 24 日

9. CF2237F *2400 比较不错的 trick。

考虑朴素的 \(O(nm)\) dp,这个是显然的。设 \(dp_{i,j}\) 表示前 \(i\) 个满足 \(i\)\(j\) 颜色的最少操作数。那么显然有 \(dp_{i,j} = \min \{dp_{i-1,j-1},dp_{i-1,m} \} + [a_i \neq j](j\ge2),dp_{i,1}=\min \{dp_{i-1,j} \}+[a_i\neq a_1]\)

这里我们需要用到一个关键的 trick,就是在固定 \(i\)\(dp_{i,j}\) 的过程中,将满足 \(i - j + m = k\) 的数据扔到 \(f_k\),加 \(m\) 保证为正整数。不难发现,这样的话 \(dp_{i-1,j-1}\)\(dp_{i,j}\)\(f\) 中下标是一致的。这样,就避免了平移操作。实际上,只要平移操作的参数固定,就可以采取作差法,将二维信息压成一维。

如此,问题就转化为了维护区间取 \(\min\)、区间加、单点修改的操作。

这个可以用线段树维护。不过这种 push_down 本人应该是第一次写,记录一下。

点击查看代码
void push_down (int id) {
    if (tag[id] < 1e8 || add[id]) {
        add[id << 1] += add[id], add[id << 1 | 1] += add[id];
        tag[id << 1] = std::min(tag[id << 1] + add[id], tag[id]);
        minn[id << 1] = std::min(minn[id << 1] + add[id], tag[id]);
        tag[id << 1 | 1] = std::min(tag[id << 1 | 1] + add[id], tag[id]);
        minn[id << 1 | 1] = std::min(minn[id << 1 | 1] + add[id], tag[id]);
        tag[id] = INF, add[id] = 0;
    }
}

10. CF1844F1 *2600 这个运用了之前的一个题(CF1009G)的 trick,即按位贪心验证可行性。

首先观察样例再手玩一下,不考虑字典序的问题,感性认为 \(c \ge 0\) 的时候升序排序,\(c < 0\) 的时候降序排序。dfs 大法跑了挺多组的都是对的。考虑证明,对于 \(c \ge 0\) 的情况,考虑三个数 \(x,y,z,x \le y \le z\)。则只需证明对于任意非负整数 \(a,b,c\) 都有 \(|a-c|+|b-c|\le|a+b-c|+|a+c|\),这个分类讨论即可证明。于是可以推广到 \(n\) 个值的时候,也就是顺序排序情况下,任意交换一对都是不优的。那么对于 \(c < 0\) 就是降序排序了。

然后需要找到字典序最小的。这种情况下,我们考虑之前 CF1009G 的 trick,就是对于当前还剩下的数,找到最小的并且满足剩下的仍然可以按照一定顺序排列排出答案的那个数,把它加入到答案序列中。而验证是容易维护的,更新也是可以通过 \(O(n)\) 维护的。

于是可以时间复杂度 \(O(n^2)\) 解决。空间复杂度则显然是 \(O(n)\)

11. CF1845E *2500 压缩 dp 状态数量的例题。没做出来。

首先,显然需要考虑的状态是最终排列一个位置是 0/1。那么一般地,考虑令 \(dp_{i,j,k}\) 表示前 \(i\) 个用了 \(j\)\(1\) 并且总花费恰好为 \(k\) 的方案数。转移显然是 \(dp_{i,j,k}=dp{i-1,j,k}+dp_{i-1,j-1,k-|pos_j - i|}\)。这样时空复杂度都是 \(O(n^3)\),虽然空间可以滚动数组压掉一维,但是时间复杂度仍没有改善。

这个时候我尝试了用数据结构等快速维护转移,但是一无所获。

这个时候,我们就要尝试压缩 dp 状态数量

观察各维性质,发现 \(i,k\) 显然是省不掉了,那么看看 \(j\)\(j\) 实际上只能在某一段范围内取到,设原序列 \(i\) 及之前一共有 \(p\)\(1\),那么一定是这个值加减 \(O(\sqrt k)\) 的范围。因为这个差值每次最多变化 \(1\),但最终花费是这些东西的总和。因此由等差数列求和公式可以发现就是在这个值左右 \(O(\sqrt k)\) 的范围。如果以变化量为一维再加滚动数组,可以将空间复杂度压到 \(O(n\sqrt k)\)

于是最终时间复杂度 \(O(nk^{\frac{3}{2}})\),空间复杂度 \(O(nk^{\frac{1}{2}})\)

7 月 25 日

12. CF1847F *2500 典题,但是我写太糖了。

注意到最终的 or 合集为:

1 2 3 4 5 6 ... n
12 23 34 45 56 ... (n-1)n n12
123 234 ...

不难发现考虑倍长原先序列,将 n12 这种看作 n1 可以,n12 也可以,那么就只需要对于每个 \(1 \le i \le n\) 找到最近的满足条件的 \(j\)

注意到 or 的性质是从一个点出发最多改变 \(\log V\) 次。于是通过这个性质可以进行初始化。初始化之后,可以求出在新序列上的坐标,然后做一个离散化+后缀 min 就可以了。

最终时间复杂度为 \(O(n \log V \log(n \log V)) = O(n \log n \log V)\),空间复杂度 \(O(n \log V)\),注意实现方式。同时不要看错题目了。

13. CF203E *2300 简单贪心。

考虑按照是否有 \(l_i \ge d\) 以及 \(c_i = 0\) 分成四类。

考虑枚举 \(c_i \neq 0\)\(l_i \ge d\) 的机器人数量。不难发现一定是按照 \(f_i\) 从小往大取,因为这样总容量就是 \(\displaystyle \sum (c_i - 1) + sum_{取的数量}\),若 \(sum = 0\) 那么很遗憾就是 \(0\) 了。随后就考虑完了 \(c_i \neq 0\) 的所有机器人,而这些机器人显然优先带 \(c_i = 0, l_i < d\) 的机器人。带上之后再按照 \(f_i\) 从大到小考虑 \(c_i = 0, l_i \ge d\) 的机器人。最后,再按 \(f_i\) 从小往大取剩余的 \(c_i = 0, l_i \ge d\) 的机器人。可以预处理前缀和二分解决。

时间复杂度 \(O(n \log n)\),空间复杂度 \(O(n)\)

7 月 26 日

14. CF1849E *2300 比较简单基础的单调栈应用。

吓哭了,为什么都是乱七八糟分治或者线段树做法??直接单调栈上二分不是更为显然吗。

我们考虑倒序枚举左端点,维护两个单调栈,一升一降。容易发现弹入一个 \(p_i\) 的时候只有一个会弹出元素。然后考虑贡献,不难发现贡献就是升的单调栈中每个元素找到右边距离最近的降单调栈中元素(可以相同的),计算距离并且与它和它后面那个元素的差取 \(\min\) 算总和。

于是维护当前合法区间数量 \(now\),然后升的或者降的单调栈中弹出元素的时候减去相应的贡献并且加上现在的贡献。中间单调栈上二分即可。

于是时间复杂度为 \(O(n \log n)\),空间复杂度为 \(O(n)\)

并且常数比较小,跑得相当快,代码也比较简洁。

15. CF1858E1 *2500 发现可以离线就是简单题了。

首先观察到它和 E2 的区别,显然就是一个强制在线,一个可以离线。那么考虑离线算法。

考虑建出操作树,不难发现树节点最多是 \(q\)。在 \(0\) 建立超级原点,那么 + 相当与新建一个点,- 操作相当于树上往上跳 \(x\) 步。注意需要对于每一步建立一个 \(pre\) 数组,这样 ! 的时候直接令当前 \(now \to pre_{now}\)。至于查询,考虑扔进 \(question_{now}\) 中,最后遍历整棵树。

添加操作和删除操作是容易维护的,开一个 \(cnt\) 数组就可以了。由于往上跳需要倍增维护,因此时空复杂度均为 \(O(n \log n)\)

7 月 28 日

16. CF1861E *2300 比较简单的 dp 题,但是我第一步设错了状态成为小丑。

我一开始其实设的是前 \(i\) 个有 \(j\) 组可行的,这个只能 \(O(n^2k)\) 并且无法用我会的东西做到更优秀的复杂度。于是我就一直卡在了这里。

实际上应该设 \(dp_{i,j}\) 表示 \(i\) 最后有 \(j\) 个互不相同,然后是容易进行转移的,最终答案也显然就是 \(\displaystyle \sum dp_{i,k} \times k^{n-i}\) 表示这个位置的贡献。不过转移有一个特殊情况,就是 \(dp_{i,k}\) 只能贡献到 \(dp_{i+1,1}\),因为选择的区间是不能相交的。

这样可以用前缀和(后缀和)优化做到时空复杂度均为 \(O(nk)\)。若加上滚动数组优化,则空间可以变成 \(O(k)\)

17. CF946G *2500 比较典的 dp。

不难发现可以转化为保留一个子序列。这个序列需要满足:设选择其在原序列的坐标是 \(i_1, i_2, ..., i_k\),则 \(a_{i_j} - a_{i_{j-1}} \ge i_j - i_{j-1}\),于是可以按 \(a_i - i\) 排序然后线段树维护区间最大值进行更新。这样可以求出不删除一个元素的情况。

随后从后往前再做一遍,设两遍求出的 dp 数组分别为 \(dp1, dp2\),则这个时候最长序列变成寻找 \(i,j\) 满足 \(a_j - j + 1 \ge a_i - i\),得到长度为 \(dp1_i + dp2_j\),不过限制是 \(j \ge i + 2\),毕竟中间需要有东西删。这个也可以用线段树进行转移,具体地,可以将 \(a_i - i\) 进行离散化。

最后两种答案取 \(\min\) 即可。

这个问题理论上可以进行加强,也就是对删除个数的数量可以多一点,但是这个是常数级别的,没啥用。如果加强了,那么无非还是套上上面的式子,离散化后转移。我这题其实应该做复杂了,做一遍 dp 设出 \(dp_{0,1}\) 数组也是可以的。意义就是删除了几个。

7 月 29 日

18. P4551 经典思路。

考虑一条路径的描述,发现就是两个端点到根节点的异或值异或一下,于是问题变成一个序列中任取两个数最大异或值,01trie 容易维护。时空复杂度均为 \(O(n \log V)\)

19. P8511 又一个经典思路,不过我没发现。

观察样例,很多都是最大的异或值!于是可以考虑从最大异或值入手。找到这个值对应的两个点 \(x,y\)(上一题的方式),然后发现除了 \(1 \sim x,y\) 的链,其他点都是这个最大值!而对于一条链进行处理是非常简单的,直接暴力添加就可以了。时空复杂度均为 \(O(n \log V)\)

posted @ 2026-07-17 17:51  kptwqc  阅读(0)  评论(0)    收藏  举报