6-8 月做题记录

P2048 [NOI2010] 超级钢琴

考虑贪心地取出当前最优解,取 \(k\) 个之后就是前 \(k\) 优解

考虑每一段解形如 \(([l, r], x)\) 表示其最优解是 \(max_{i=l}^r\{s_x - s_i\}\) 那么每次从堆顶取出最优解 \(s_x - s_t\) 后将原区间分裂成两个 \([l, t - 1], [t + 1, r]\) 再扔进堆里就行了

8.12

A

昨天 B 的弱化版

B

发现这是一个 DAG,所以可以 DP,设 \(f_i\) 为以 \(i\) 到达 \(i\) 的字典序最大的序列

对,你没看错,直接维护序列

由于每次只会往序列中加入一个数,所以可以用可持久化线段树维护序列中 \(1\sim n\) 的出现次数

现在瓶颈在于比较两个序列的字典序大小,我们只需要找到两个线段树中第一个不相等的位置比较大小就行了,在线段树上二分,判区间相等可以哈希,\(O(\log n)\) 做完了

C

首先对条件进行变形,变成 \(\mathrm{xor}=\mathrm{or}-\mathrm{and}\)

然后每一位是独立的,可以分开考虑,

设所选集合为 \(S\)\(\mathrm{or}=R, \mathrm{and}=L, \mathrm{xor}=D\)

那么对于每一位 \(i\), 有三种情况:

  1. \(R_i=0, L_i=0, D_i=0\),所选的所有位都是 \(0\)
  2. \(R_i=1, L_i=0, D_i=1\),有 \(0\)\(1\) 的个数是奇数
  3. \(R_i=1, L_i=1, D_i=0\),全都是 \(1\),并且 \(1\) 的个数是偶数

现在似乎直接对着这些条件做能有 \(V^{100}\) 做法,考虑简化一下条件

限制 3 中 \(1\) 的个数即为 \(|S|\),所以每一位可以少一些信息,能够做到 \(V^3\)

所以现在就是每一位都是这三种情况之一

  1. \(0\)
  2. \(1\) 的个数为奇数且有 \(0\)
  3. \(1\)\(|S|\) 是偶数

然后我们进一步挖掘条件的含义,条件 2 是最复杂的,考虑有没有什么性质,

观察部分分,考虑所有数都是奇数的时候,\(|S|\) 一定是偶数,那么注意到由于 \(1\) 的个数是奇数,那么必然有 \(0\),条件自动满足

所以是说,只要有一位全 \(1\),条件 2 就变成 \(1\) 的个数为奇数,相当于我们知道异或是多少,这个可以用线性基统计方案数

现在考虑没有全一的情况,发现有 \(0\) 这个条件是不好做的,考虑容斥,钦定一些位全是 \(1\),依旧线性基

至于线性基,我们发现每次要用到某些位已经确定的所有数的线性基,共有 \(3^{14}\) 种,直接做似乎需要把每个数都插入到 \(2^{14}\) 个线性基里面,但是由于线性基满秩之后就加不进去了,所有有大部分操作都是浪费的,所以其实可以递推合并线性基,复杂度就变成了 \(3^{14}\times14^2\),可以通过

8.17

P14301 日本沉没 2 / Japan Sinks 2

一开始的想法:

  1. 线段树维护单调栈,不太行
  2. 每个点从一个时间点开始可以被修改,考虑在这个时间点加入点,然后直接维护可被修改的点

进一步思考:

从 2 出发,考虑求出每个点被加入的时间,按值域从大到小扫描,考虑它前后第一个比它大的点,然后基于它们的时间算出该点时间

但是难写爆了,还是离线的

尝试改成在线:

考虑维护每个点目前还有多少次操作才能被修改,也就是 前缀 max - \(a_i\),每次全局减 \(1\),加入变成 \(0\) 的点

题解:

上面的思考都需要基于维护原序列,所以十分复杂,我们不需要直接维护原序列,可以维护序列的轮廓,也就是每个位置的 \(\min(\text{prefix max}, \text{suffix max})\),问题就被大大简化

8.19

P14638 [NOIP2025] 序列询问

感觉去年的我真是菜爆了

先看部分分,\(L = R\) 是好做的,两遍单调队列解决

考虑拓展至原问题,原问题是在 \(i\) 处统计贡献,不太好,我们想在端点处统计贡献,把贡献作为端点的权值,然后对于每个 \(i\) 查区间 max 得到答案

考虑对于一对 \(i, r\)\(r\)\(ans_i\) 的贡献是:

\[s_r - \min_{l < i \land r - l\in [L, R]} s_l \]

我们发现 \(l < i\) 这个限制非常烦,它导致每个 \(r\) 对不同的 \(i\) 贡献是不一样的,考虑硬去掉这个限制

去掉之后会导致一些 \(r\) 被漏算了,也就是说,如果 \(r\) 的决策区间 \([r - R, r - L]\)\(i\) “截断” 了,这个 \(r\) 就不能记入贡献,否则会导致贡献多算,但是这也导致没有被截断的那部分的贡献被漏掉了

具体的,就是 \([i + L, i + R - 1]\) 里面的这些 \(r\) 被漏掉了

这时有一个想法:\(r\) 不行,我们还有 \(l\) !考虑反过来做一遍,能够覆盖到一部分漏掉的贡献,类似地,\([i - R, i - L - 1]\) 里面的 \(l\) 被漏掉了

一个区间被漏掉 iff 它的左右端点都在上面说的 “禁区” 内,于是我们发现,我们漏掉的区间长度 \(\ge 2L + 1\),所以如果 \(2L \ge R\),那么实际上所有合法区间都被覆盖了

观察部分分,发现我们会了特殊性质 D

考虑怎么把原问题转成 \(2L \ge R\),可以将询问拆成 \(O(\log n)\) 个区间,得到 \(O(qn\log n)\) 做法

考虑倍增分块,我们可以拆成 \([L, 2^p], [2^p, 2^q], [2^q, R]\) 这三个区间(\(2^{p-1} < L \le 2^p, 2^q \le R < 2^{q + 1}\)),中间的 \([2^p, 2^q]\) 只有 \(O(\log^2 n)\) 个,可以 \(O(n \log^2 n)\) 预处理

最终我们做到了 \(O(n \log^2 n + qn)\)

此外这个题还有建系硬做、决策单调性等做法

8.24

P8124 [BalticOI 2021] From Hacks to Snitches (Day1)

太难了太难了太难了

模拟赛中光荣获得 15 pts

称不在环上的点为安全点

先考虑特殊性质,发现我们一定可以在安全点等待一段时间,然后再走到环上,不会出现从环 A 走一条边到环 B,然后需要在 A 这个地方等待但是碰到守卫的情况

由于状态中要体现守卫位置,考虑设状态 \(f_{u, s}\) 表示到 \(u\) 的时候守卫在环上第 \(s\) 个点的最小时间,如果 \(u\) 是安全点,则 \(s = 0\),设 \(u\) 所在环长是 \(l_u\),那么 \(s\) 其实就是到达时间 \(T \bmod l_u\)

那么考虑转移 \(u \rightarrow v\),到 \(u\) 的时间是 \(T\)

  1. 原地等待 \(O(1)\)
  2. 安全点/环上点 \(\rightarrow\) 安全点:直接转移即可 \(O(1)\)
  3. 安全点 \(\rightarrow\) 环上点
    考虑我们走到 \(v\) 然后在环上等待,但是我们可能被守卫发现,所以有用的转移只有两种:在 \(T + 1\) 时刻走到 \(v\)、等守卫刚好走过 \(v\) 的时候从 \(u\) 走到 \(v\),也就是转移到 \(nxt\) 对应的时刻
  4. 环上点 \(\rightarrow\) 环上点(走环上边)
    由于需要考虑与守卫擦肩而过的情况,需要单独处理,每次转移即可,单个状态 \(O(1)\),总复杂度 \(O(L^2)\)
  5. 环上点 \(\rightarrow\) 环上点(走非环边)
    在特殊性质里面这两个点一定是同一个环上的,仿照 case 2,我们发现两个点一定不会同时有守卫,所以可以先在 \(u\) 等待,等守卫来了就走到 \(v\),然后再回到 \(u\),相当于 \(u\) 不受守卫影响,所以跟 case 2 一样处理即可
    但是现在一条边要转移 \(O(L)\) 次,转移是 \(O(mL)\) 的,TLE
    考虑第一次转移 \(f_{u, s}\) 后,后面所有其他的 \(f_{u, s'}\) 都会被这次转移通过等待偏序掉,所以转移一次之后就可以把 \(u \rightarrow v\) 这条有向边删掉!!

现在对于 A 性质,我们做到了 \(O((m + L^2) \log(m + L^2))\)

考虑一般情况其实就是多了一种 case:

  1. 环 A 上点 \(\rightarrow\) 环 B 上点(两个点不在一个环里)

设在 \(T\) 之后 \(v\) 的守卫第一次到达 \(v\) 的时间是 \(t\),那么如果 \(t\) 时刻 \(u\) 处没有守卫,我们依然可以沿用上面 case 4 的做法,\(O(1)\) 转移,并删掉这条边

如果同时都有守卫的话还需要再处理一下,这里直接贺 BM 的题解:

假设当前状态为 \(f_{u,s}\)​,则为了保证正确性,我们需要让所有的 \((pos=u,time=s+kl)\) 都转移出去。由于没有转移 \(nxt\),我们应当考虑第一个超过 \(nxt\) 的时刻 \(t'=s+k_1l\),并使用 \(t'\) 进行上述两种转移。假如可以进行 \(nxt\) 转移,则不用考虑之后的 \(s+kl\) 了;否则,如果仍然不能进行 \(nxt\) 转移,说明环长之间有倍数关系,陷入了跟之前一样的境地。可以证明此后的转移都没有用,依然不用考虑。另外,由于 \(u\) 一开始多绕了几圈,无法保证最优性,因此不论是上述哪种情况,都无法删去这条边。

\(u, v\) 所在的环分别为 \(a, b\)
考虑根据 “u 的守卫到 u 时,v 的守卫也到达 v” 这个条件,点 \(u\)\(b\) 的边至多还剩 \(\lceil l_b / l_a \rceil\) 条,所以环 \(a, b\) 之间的总边数 \(\le l_b + l_a\)

每条边至多被环 \(a\) 的所有 \(l_a\) 个状态更新,当 \(l_a \le l_b\) 时,复杂度 \(O(l_a l_b)\)

\(l_a > l_b\) 时,更精细的分析说明一条边至多被更新 \(l_b\) 次就会被删除,考虑我们如果用 \(f_{u, s}\) 转移,但边 \(u, v\) 没被删掉,一个必要条件是 \(s\) 所代表的守卫位置与 \(u\) 的距离不超过 \(l_b\),这只有至多 \(l_w\)\(s\),所以复杂度仍然是 \(O(l_a l_b)\)

最后总转移数是 \(O(m + L^2)\),Dijkstra 可以跑过

8.27

静态顶树

给一棵 \(n\) 个节点的树 \(T = (V, E)\),定义一个边集 \(S \subseteq E\) 的价值 \(f(S)\) 为:\(S\) 中的 每个连通块中点权的最小值 之和

\(\sum_{S \subseteq E} f(S) \bmod 998244353\)

计数题别拆贡献

模拟赛中起手将贡献拆到最小值点上,全剧终

实际上可以直接 dp,设状态为 \(f_{u, x}\) 表示 \(u\) 所在连通块最小值是 \(x\) 的方案数,转移如下:

  1. 断边:\(f_{u, x} \times f_{v, y} \rightarrow f_{u, x}\)
  2. 不断边:\(f_{u, x} \times f_{v, y} \rightarrow f_{u, \min\{x, y\}}\)

直接 dp 是 \(O(n^2)\) 的,考虑转移在做什么

  1. \(f_{u, x} \times \sum_{y} f_{v, y} \rightarrow f_{u, x}\)
  2. \(f_{u, x} \times \sum_{y \ge x} f_{v, y} + f_{v, x} \times \sum_{y > x} f_{u, y} \rightarrow f_{u, x}\)

这个可以类似 P6773 [NOI2020] 命运 一题,用线段树合并维护

8.29

P5330 [SNOI2019] 数论

赛时想写 bitset,但是最后没写完,打了暴力得了 90pts

bitset 有 \(O(\frac{PQ}{w})\) 做法

考虑要求的这个东西是以 \(\mathrm{lcm}(P, Q)\) 为周期的,所以可以先求一个周期的,然后再求 \([0, T \bmod \mathrm{lcm})\) 的答案

考虑相当于对于每个 \(a \in A\),求满足 \(a + kP \bmod Q \in B\)\(a + kP < T\) 的 k 的个数,

考虑枚举 \(k\),然后每个 \(a\) 就对应一个 \(b\),答案就是 \(A\) bitset 里面的一段点乘 \(B\) 里面的一段,手写 bitset 暴力做就行了,可是我 bitset 写挂了

由于数据水,跑到了第四优解

正解是这样的,考虑 \(Q\) 的剩余系构成一个环,\(B\) 里面的点是有标记的,从一个 \(a \in A\) 出发,每次 \(+P\),跳的步数有上限,求能跳到多少个有标记的点,直接把每个数跳的轨道取出来维护就行了

P6009 [USACO20JAN] Non-Decreasing Subsequences P

起手 DDP,考虑用矩阵维护转移

但是线段树维护矩阵乘法只能做到 \(O(nk^3 + qk^2\log n)\),跑不了一点

赛时做到这的时候考虑去掉线段树,考虑把区间的询问差分成前缀,我们发现转移矩阵正好有逆,所以可以预处理前缀矩阵之积与矩阵逆之积,询问复杂度就可以去一个 \(\log\),做到 \(O(nk^3+qk^2)\)

赛时写这个稍微卡了下常就过了(注意到上面涉及到的矩阵都是下三角矩阵,所以可以剪枝优化下常数)

实际上,注意到上面的矩阵都是稀疏的,每个位置的转移矩阵只有 \(O(k)\) 个位置有值,所以矩乘其实可以做到单次 \(O(k^2)\),求逆也可直接手动构造,不用高消求逆

不过,当我们遇到信息合并复杂度较大时,可以考虑猫树分治

考虑对于每一层预处理出来夸过区间中点的贡献

对于左半边区间,预处理出每个 \([i, mid]\) 中以 \(x\) 结尾的不降序列个数,右半边则数每个 \([mid+1, i]\) 中以 \(x\) 开头的不降序列个数,从而可以 \(O(k^2)\) 拼接,合并次数由 \(O(\log n) \rightarrow O(1)\),复杂度 \(O(nk^2 \log n + qk^2)\),可以通过

P17286 「IXOI R2」Horizon Blue

依旧考虑分治,考虑在线段树上将询问区间拆成 \(O(\log n)\) 个区间直接回答答案,并处理区间之间的贡献

考虑怎么处理跨过区间中点的子区间贡献,设每个 \([mid+1, i]\) 位置的前缀 max 为 \(f_i\),考虑这个 max 能覆盖到左半区间的哪个位置,设其为 \(g_i\),对于左半边区间同理

最开始可以直接算出 \(f_i, g_i\),然后考虑怎么在修改的时候维护,考虑 \(f_i\) 实际上是维护序列轮廓,并且 \(f, g\) 都是单调的,由于每个位置的值单调不降,所以每次只需要对 \(f, g\) 进行区间覆盖即可

代码有点长,比较训练代码

8.31

P7962 [NOIP2021] 方差

首先操作等价于在差分数组上交换 \(d_i, d_{i+1}\),所以任务是找到一组 \(d_{2}, d_{3}, \cdots, d_{n}\) 的排列,使得它们的前缀和数组的方差最小

注意这里并没有考虑 \(d_1\),因为方差刻画的是数据的离散程度,所以我们并不关心每个数的具体值,而只关心他们在数轴上的相对位置,因此可以给整个序列都减掉 \(d_1(=a_1)\)

考虑方差是 \(E(x^2) - E^2(x)\),即 \(\frac{1}{n} \sum a_i^2 - (\frac{\sum a_i}{n})^2\),所以我们只关心当前的 平方和 与 和,记下当前用了那些数 以及 和,可以获得 \(O(2^n nA)\) 做法

观察性质,考虑邻项交换,感受一下:方差刻画的是数据离散程度,所以如果一个数充分大于平均值,如果对它进行操作之后会变得更小(即更接近平均值),那么数据就更紧凑,方差就更小

所以我们发现,差分最后一定排成一个单谷序列

考虑按值域从小到大扫描这个谷,以谷的位置为基准,偏移序列,方差不变,每次我们可以决定把一个差分值放在左边/右边

状态记下标、左半边差分和(以得到现在要插入的 \(a_i\) 是什么),总和(\(\sum a\)),可以得到 \(O(n^2 A^2)\) 做法

考虑优化状态,尝试优化 \(\sum a\) 无果,考虑优化“左半边差分和”这一维,我们记这一维是要知道当前要加入的 \(a_i\) 是多少,如果去掉这个怎么办?

那么我们要加入的 \(a_i\) 只能来自于当前考虑过的所有差分的和,考虑把基准点换成谷的左边缘,那么在左边插入相当于所有数 \(+d_i\),并插入一个 \(0\),在右边插入相当于插入 \(\sum d + d_i\),两种情况都能计算平方和,可以转移

所以就做到了 \(O(n \cdot nA)\),好像过不了最后一档

但是注意到由于值域很小,所以差分里面有大量的 \(0\),不用转移,所以实际上只用考虑 \(\min(n, A)\) 个差分值!

复杂度是 \(O(\min(n, A) nA)\)

P14637 [NOIP2025] 树的价值

首先想到之前处理 mex 的一些经验,例如 MEX counting 等延后计算题,我们考虑设状态 \(f(u, w, k)\) 表示 \(u\) 的子数内已经有 \([0, w - 1]\),有 \(k\) 个点 \(\ge w\) 的最大价值,那么合并子树时就是对 \(w\) 这一维 chkmax,对 \(k\) 这一维做树上背包,然后还可以 \(f(u, w, k) \rightarrow f(u, w + 1, k - 1)\),合并完 \(u\) 的所有子树后再对 \(f(u, w, k)\) 加上 \(u\) 子树价值 \(w\),这样我们就有 \(O(n^3)\) 做法,获得 48 pts

我们还没有用到树高很小的条件,考虑怎么用这个条件

进一步思考我们的转移在干什么,我们发现状态里面有一维是 chkmax,所以实际上这一维的贡献只来自于 \(w\) 最大的那个儿子,其他儿子的 \(w\) 都不重要

我希望通过这个思路简化状态,企图让 \(w, k\) 不同时在状态里面出现,但是做不到

实际上,考虑刚才我们发现了什么,我们给每个节点钦定了一个儿子作为 \(w\) 最大的那个(下文称其为重儿子),这实际上得到了原树的一组链剖分

在这个链剖分的意义下刻画贡献形式,每个点的价值 = 重儿子的 mex + 子树里面的一部分空闲点

考虑刻画空闲点,或者说每个点 \(u\) 对答案的贡献,它会贡献到祖先链上的一个点 \(x\),然后 \(x\) 到它所在链端点这条路径上所有点的权值都会 \(+1\),所以设 \(x\) 在链中的深度为 \(c_x\),那么 \(u\) 的贡献就是 \(c_x\),显然 \(u\) 应该贡献到 \(c_x\) 最大的 \(x\)

所以现在问题就变成钦定一组链剖分方案,使得

\[\sum_{u} \max_{u \in T_x} c_x \]

最大

直接对这个东西 dp,记录当前点 \(c_u\),以及祖先链上最大的 \(c\),可以获得 \(O(nm^2)\) 做法,获得 76 pts

考虑优化状态,我们记录 \(c_u\) 是为了得知儿子的 \(c\)然后与当前最大的 \(c\) chkmax,从而得到正确的价值,那 chkmax 显然就可以分段考虑,假设现在 \(c_u = 1\),祖先链上最大 \(c\)\(c_{max}\),那么当 \(u\) 所在链长 \(l \le c_{max}\) 时,当前链长就没有用,如果 \(l > c_{max}\),那 \(c_{max}\) 就变成了 \(l\),只用记一个数

76 pts 的 dp 动态维护了当前 \(l\)\(c_{max}\) 的大小关系,但是我们也可以暴力枚举分界点,直接确定大小关系

具体地,针对这两种情况分别设状态,设 \(g_{u, j}\) 表示 \(c_{max} = c_u = j\) 时的最大价值,设 \(f_{u, j}\) 表示 \(c_{max} = j,\ c_u = 1\) 的最大价值

\(g\) 的转移是容易的,考虑 \(f\) 怎么转移

枚举一个到 \(u\) 距离为 \(j - 1\) 的点 \(v\)\(v\) 就是分界点,那么有

\[f_{u, j} = j (j - 1) + g_{v, j} + \sum_{x} f_{x, j} \]

这里的 \(x\) 是指所有挂在 \(u, v\) 路径(不包括 \(v\))上的点,后面这个求和式可以用树状数组维护,每次儿子对这个和式的贡献是子树加的形式

每次 \(f\) 转移需要枚举子树中所有点,复杂度是 \(O(\sum siz_u) = O(\sum dep_u) = O(nm)\)

这样就做到了时间 \(O(nm \log n)\),空间 \(O(nm)\),100 pts

本体要点:

  1. 链剖分与贡献关系的刻画
  2. chkmax 分段维护
posted @ 2026-08-25 10:36  zhm0725  阅读(7)  评论(0)    收藏  举报