XYD-NOI | 解题技巧
基本技巧
-
01 串转化为走网格图。如果是 \(1\) 向右,\(0\) 向上的话,顺序对数就是面积大小。
-
括号序列转折线图。可以和反射容斥结合。
-
区间包含/不交关系:树。括号串也可以看成一堆区间。
-
走若干步回到自己,考虑置换环相关。\(i\to a_i\) 构建基环树。
-
逆序对和偏序信息可以放到二维平面处理。
P9338 [JOISC 2023] Chorus (Day3)
转化为折线图,\(A\) 向右上走,\(B\) 向右下走。最严格条件是 \(k\) 个连续单峰函数,每个峰端点高度均为 \(0\),也就是底座在坐标轴上。
由于不是连续选择元素,而是可以选择子序列,所以只要当前折线画出来之后轮廓包含住了上述最严格折线必然合法,这个条件是充要的。如图 \(k=5\) 的时候,黑色曲线就是其中一个最严格折线,而红色折线这个时候包含住了黑色折线,因为它也是合法的。

考虑何时能够包含住最严格曲线,对于当前序列的曲线画出来之后,我们能在当前曲线下方任意行走。由于我们可以 ABAB 这样子快速上下来构造峰,所以峰的个数很容易多,只要我们能走出 \(\le k\) 个峰就一定能走出 \(k\) 个峰(总长一定的情况下,把大峰拆成若干小峰就可以增加个数),所以贪心地尽可能走出尽量少的峰。
可以现在我们在目前折线下不一定能走出 \(\le k\) 个峰,所以需要将原折线进行翻折使得其去包含住一些峰。
交换相邻 \(A,B\) 其实就是把一个谷低/峰进行翻折(本题翻折峰没有意义)。翻折一大段的代价就是其面积,翻折面积的意义为逆序对数量。据此进行 dp。借用一张 Rainbow_qwq 博客园里面的图,黑色的是原折线,红色的是最严格折线。我之前试过很多对于黑色折线 dp 的方式,但是都失败了,因为黑色折线波动的规律其实是不多的,我找到了一些但是都不充要,所以我们考虑对于红色折线进行 dp,然后让黑色折线去满足红色折线的要求,这样子就不需要对于黑色折线的形态去刻画充要条件了。

设 \(f_{i,j}\) 表示对于前 \(i\) 个位置划分出 \(j\) 个最严格峰的最小代价,其中 \(i\) 表示第 \(j\) 个峰结束的位置,就是最后一个峰下降到的最低点。
一个转移 \(f_{k,j-1}\to f_{i,j}\),会有一个贡献函数 \(\rm cost(l,r)\) 来表示这个代价。就是把 \([l,r]\) 的黑色折线翻折到红色折线的上方的代价。其中这个红色折线已经固定了,就是一个以 \((l,0)\) 和 \((r,0)\) 为两个底部顶点的等腰直角三角形。
上文说过翻折一大段的代价就是其面积,所以黄色部分就是其代价。观察一下图形面积,一段向右下的线段能贡献面积需要一段在 \(x\) 轴下方的向右上的线段。考虑如何计算这个面积,在坐标轴中根据直线方程分成两段就是 \(\sum \max(x-l-s_x,0)+\sum \max(r-x-s_x,0)\),暴力转移是 \(O(n^3)\) 的。

观察到 \(f\) 的第二维关于 \(k\) 是凸的,可以 wqs 二分处理掉。
同时 \(\rm cost(l,r)\) 满足四边形不等式,我们可以用二分队列转移,这个时候时间复杂度为 \(O(n\log^2 V)\)。可惜 \(2\log\) 过不去。
\(\rm cost(l,r)\) 函数其实是可以改写成一个去掉 \(\max\) 的形式的,因为单次 \(s_x\) 最多变化 \(1\),所以 \(x-l-s_x\) 是单调递增的,能使其 \(\ge 0\) 的位置是一段后缀。对于 \(r-x-s_x\) 同理。我们提前处理出来这两个转折点,就可以拆掉 \(\max\) 写成斜率优化的形式了。
写成斜率优化的形式之后就可以丢掉一个 \(\log\) 了。时间复杂度 \(O(n\log V)\)。
其实上述又带 \(l\) 又带 \(r\) 的式子很复杂,把坐标轴旋转 \(45°\) 之后再计算这个贡献会变成很简单!这启发我们在求解这种 \(01\) 序列的时候,观察性质用走斜线画图,计算贡献用旋转 \(45°\) 之后的方格图来算。
P11420 [清华集训 2024] 乘积的期望
Trick:对于定长度区间操作经常可以转化为网格图平衡复杂度,印象中 QOJ4893. Imbalance 也是的。
考虑将 \(n\) 个元素划分成如下的网格图(\(n\) 不是 \(m\) 的倍数的时候就补 \(b_i=0\) 凑成一个完整的图)。

可以发现每次选择 \(m\) 个连续的位置一定是某一行的后缀搭配上下一行的前缀,如图中蓝色部分。
有两种转移方式,一种是从上往下行间转移,另一种是从左往右在列上面进行转移。如果我们能找到两种转移方式就能平衡复杂度。
首先,将所有元素乘积的期望转化成组合意义,也就是对于所有覆盖方案我们都对于每个位置选择一种覆盖其的操作的总方案数。我们可以对着这个进行 DP。
竖向 DP
思考一下 DP 需要记录哪些状态,首先有记录当前的位置 \(i\),其次还有当前有几条操作线段延伸过来了,可以我们发现这还不够,无法刻画长度为 \(m\) 这个约束。于是把这个改为一个 \(m\) 位的二进制数 \(s\),记录该位置往前的 \(m\) 个位置中有哪些作为线段的开头,这还不行,因为可能有多个线段共用一个开头,其实这个约束可以改为选这个线段的第一个元素。所以就变成了,某个位置是否是一条线段中被选择的第一个位置,这样子在一个位置上只会和一条线段有关,就可以用 \(01\) 变量记录了,这样子我们只需要约束选这个线段的第一个元素和最后一个元素距离 \(\le k\) 即可。
这样子我们就可以根据信息开闭线段了。同时,我们发现上述过程并没有对于所有线段作出区分,于是记录一下我们已经选了的线段数量 \(j\),这样子最后为了让这些线段可区分,我们就乘以 \(A^j_C\times (\sum b_i)^{m-j}\) 的系数。最后除以 \((\sum b_i)^m\) 即可。
设计好了状态,现在需要进行 dp。考虑当前这一个位置 \(i\),我们要为其选择一个覆盖它的线段(不考虑无线段覆盖它的情况,因为这种情况的贡献为 \(0\))。
首先,如果 \(i-m+1\) 这个位置被选择了开头第一个元素,但是没有选择收尾元素,我们就必须选择这条线段,因为这条线段最远也就延伸到 \(i\),而我们加入的时候已经钦定过其有结尾元素了,所以必须要为它找一个结尾元素,那么就只能选择 \(i\) 了。
否则,我们可以自由选择。第一个选择就是在 \(i\) 这个位置新开一条线段(不一定以 \(i\) 作为左端点,但是 \(i\) 一定是其第一个元素),注意这个线段有两种选择,要么在这里终结,也就是说开头结尾都是 \(i\);要么往后延伸,也就说我们还要为其找一个 \(\neq i\) 的结尾。
如果不选择新开线段,我们可以为其挑选一个之前开的线段,钦定其覆盖 \(i\)。这里还有两种选择,一种是这条线段在 \(i\) 终结,也就是说 \(i\) 是其最后一个元素,注意由于我们加入线段的时候没有为其选择左端点 \(l\),所以在终结的时候要选择一个,假设其第一个位置是 \(j\),那么左端点的范围就是 \([i-m+1,j]\),求一下区间 \(\sum b_i\) 作为系数即可。如果这条线段不在这里终结,那很简单,正常转移就行了。
分析一下时间复杂度,状态数 \(O(nc2^m)\)。注意到最多有 \(O(n)\) 条线段被选择,所以状态数可以被优化到 \(O(n^22^m)\),转移是 \(O(m)\) 的,所以是 \(O(n^2m2^m)\)。当 \(2m>n\) 的时候,中间的 \(2m-n\) 个元素会被所有操作都覆盖一次,所以这部分贡献可以直接算的,是 \(c^{2m-n}\),然后把这些部分去掉,就可以让 \(2m\le n\) 了。所以指数可以对于 \(\dfrac{n}{2}\) 取 \(\min\),最后的复杂度就是 \(O(n^2m2^{\min(m,\frac{n}{2})})\)。分析一下分数表格,发现在指数为 \(16\) 左右的时候是可以过的,因此这个做法是可以做 \(m\le 16\) 或者 \(n\le30\) 的测试点。这样子可以得到 \(60\rm pts\)。
横向 DP
上述做法可以通过 \(m\le 16\) 或者 \(n\le 30\) 的部分分,所以剩下的数据范围就是 \(m\ge 17\) 且 \(31\le n\le 50\),可以发现这个时候本题解配图中的 \(k=\lceil \dfrac{n}{m}\rceil=3\),也就是说网格图分为三列。而我们横着做 DP,就是每次的状态是一列。
尝试观察一些刻画方式,可以发现每次操作必然是对于每一列的三个位置中恰好有一个位置被操作。所以 \(a_i+a_{i+m}+a_{i+2m}=m\)。
还有就是基本上所有线段都是跨越两行的,这会导致交界处被覆盖次数很多。所以第一行中的 \(a_i\) 单调递增,第三行中的 \(a_i\) 单调递减。
这两个条件足够了吗?并不是的。还有一个约束就是你会发现,要么一次操作是行内的,要么一二联动,要么二三联动,唯独没有一三联动的情况,所以 \(a_m+a_{2m+1}\le C\)。
可以证明这三个条件已经是充要条件了。可以对于这个结构按照列进行 DP。
对于第三个约束,我们直接外层枚举 \(a_m\) 就可以解决了。对于第一个约束告诉了我们,我们只需要记录三个位置中的其中两个位置的值,然后第三个位置的值就可以通过做差直接得到了。那么是记录哪两个位置呢?为了方便限制第二个约束,我们记录第一行和第三行的就就行了。设 \(f_{i,j,k}\) 表示 dp 到了第 \(i\) 列,其中 \(a_i=j,a_{i+2m}=k\) 的方案数。
转移的时候,枚举 \(j'\ge j\) 和 \(k'\le k\) 进行转移即可。直接做的时间复杂度是 \(O(nC^5)\)。
有一个小 trick 就是,你发现 \(j,k\) 之间是相对独立的,所以你可以分开转移 \(j,k\),也就是说 \((j,k)\to (j',k)\to (j',k')\),这样子就优化到了 \(O(nC^4)\)。可以拿到 \(90 \rm pts\)。
发现瓶颈在于 \(C\) 很大,有一个观察就是你在竖向 DP 的时候,会发现整个 DP 过程都是和 \(C\) 无关的,只有最后乘以组合数的时候才涉及 \(C\),最后是一个关于 \(C\) 的 \(1\) 到 \(n\) 次的多项式求和,所以最终的答案关于 \(C\) 是一个 \(n+1\) 次多项式。于是我们算出 \(C\in [1,n+2]\) 的答案,然后进行拉格朗日插值即可。
这部分的时间复杂度是 \(O(n^6)\)。
两个做法综合起来可以通过本题。
CF1637H Minimize Inversions Number
选择单个元素 \(p_i\),逆序对数会变化 \(\sum\limits_{j<i}[p_j<p_i]-\sum\limits_{j<i}[p_j>p_i]\),记为 \(w_i\)。
选择多个元素,下标序列为 \(a_1,a_2...a_k\),变化 \(\sum w_{a_i}+2\times [逆序对数-顺序对数]\)。
其中逆序对数 \(-\) 顺序对数可以转化为 \(2\times [逆序对数]-{k\choose 2}\)。
尝试打表,可以发现选择 \(i+1\) 个数的方案恰好是选 \(i\) 个数的方案基础上再选择一个数。
有了这个观察就很好做了,我们只需要满足单步最优,累计起来就是多步最优了。否则你很难刻画选择子序列一堆数中逆序对数。
直接动态维护加入每个 \(a_i\),会产生多少代价,每次选择最小代价的加入。难以单 \(\log\)。
需要发现一些性质,将每个点看成 \((i,p_i)\) 放到二维平面中。采用调整法,对于 \(i<j\),选择 \(p_j\) 一定是更优的。对于 \((i,p_i)\) 和 \((j,p_j)\) 为分界点,平面划分出来的若干个区域进行分别讨论即可,对于单个区域贡献不劣。
于是上述加入某个数 \(p_i\) 之前,对于所有 \(j>i\) 且 \(p_j<p_i\) 的数已经被添加过了,所以上述第二项子序列逆序对数贡献对于单个元素可以提前确定了,单个数的贡献可以变成独立的了,不会依赖于之前选择了什么而变化,直接排序即可。时间复杂度 \(O(n\log n)\)。
打表
-
观察答案的结构/性质。
-
找到充分必要条件。
-
构造题分析子问题。
QOJ9785.Shrooks
给定一个长度为 \(n\) 的未填满排列 \(p\),求有多少排列满足对于 \(\forall i,j\),都有 \(|p_i-p_j|+|i-j|\le n\)。其中 \(n\le 2\times 10^5\)。
可以发现这是一个很严的限制,考虑最难满足的地方,\(1\) 和 \(n\) 肯定相邻,\(n-1\) 和 \(1\) 的距离最多为 \(2\) 等等。尝试打表,观察一下,发现 \(1\) 和 \(n\) 必然是在序列中间的,然后小数和大数向外散开。以中间位置为划分点,小数满足递增,大数满足递减。
放入小数 \(1\),会限制大数的位置,放入大数 \(n\) 为限制小数的位置。可以进行 dp,根据形态要求可以把状态数压到 \(O(n)\)。
QOJ8546. Min or Max 2
给定排列 \(a,b\),你手上有一个数对 \((x,y)\),初始为 \((a_1,b_1)\)。依次从 \(2\to n\),你每次可以让 \((x,y)\to (\max(x,a_i),\max(y,b_i))\),也可以让 \((x,y)\to (\min(x,a_i),\min(y,b_i))\)。对于最后得到的所有 \((x,y)\),求 \(|x-y|=k\) 的个数。\(k\) 取 \([0,n-1]\) 之内的 所有数。\(n\le 5\times 10^5\)。
打表可以发现满足要求的 \((x,y)\) 必定是形成一个区间阶梯状,也就是对于每个 \(x\),合法的 \(y\) 构成一个区间,且随着 \(x\) 增大,这个也区间左右端点也不断向右移动。
如何判定 \((x,y)\) 是否合法,对于 \(<x\) 设为 \(0\),\(>x\) 设为 \(2\),\(=x\) 设为 \(1\)。然后进行一个双指针。可以放到线段树上做动态 dp,求出所有边界之后这题就做完了。
调整法
- 符合大多数位置,再进行微调。
- 每步不劣。
调整之后就是符合若干条件的子问题了。
尝试弱化问题条件。
P11986 [JOIST 2025] 急救车 / Ambulance
四个医院还是太复杂了,于是先弱化一下问题,将 \(4\) 个医院变成 \(2\) 个,且两个医院处于对角线。
设第 \(i\) 个人到医院 \((1,1)\) 的代价为 \(a_i\),到医院 \((L,L)\) 的代价为 \(b_i\)。可以发现 \(a_i+b_i\) 为一个定值,于是就是一个经典贪心,直接按照 \(a_i\) 排序后,前缀分配给医院 \(1\),后缀分配给医院 \(2\)。
扩展到四个医院,还是按照刚刚的对角线划分。我们这次引入两个对角线序列,\((1,1)\) 和 \((L,L)\) 一组,\((L,1)\) 和 \((1,L)\) 一组。对于第一组按照到 \((1,1)\) 的距离排序,对于第二组按照到 \((1,L)\) 的距离排序。
枚举两个序列中的前后缀分界点,可以将所有点分成四组,每一组中的点可以前往两个地点。于是我们直接设计一个 dp,设 \(dp_{i,t}\) 表示考虑了这一组的中的前 \(i\) 个元素,第一个医院用时为 \(t\) 的时候,第二个医院的最小用时。对于四组分别 dp 之后,枚举 \((1,1)\) 的用时,然后利用第一组和第二组的 dp 信息可以得到到 \((1,L)\) 和到 \((L,1)\) 的最小用时,利用第三组的 dp 信息可以得到 \((L,L)\) 的最小用时,再利用第四组的 dp 信息判定 \((L,L)\) 和 \((L,1)\) 当前的用时是否合法即可。
单次 dp 是 \(O(nT)\) 的,如果直接枚举分界点就是 \(O(n^3T)\) 的。可以对于第一个分界点扫描线,维护下标为第二个分界点的前后缀 dp 值,这样子就可以做到 \(O(n^2T)\) 了。
CF2023F Hills and Pits
弱化问题条件,全局 \([1,n]\),固定起点终点。
UOJ949. 【UER #12】电网检修
先观察一下电子运动规律,自己模拟一下可以发现是左右交替撞相反运动方向的段,被撞过的段每次折返的时候都不会被撞了,会恰好顺着电子运动趋势。而且恰好是左边撞一个右边撞一个。根据这个观察已经可以刻画终态了。
那就看哪边先被撞开呗,于是可以得出结论如果 \([1,i]\) 中 + 的个数(记其为 \(c\)) \(\le\) \([i+1,n]\) 中 - 的个数,那么电子会从左边出去,根据 \([1,i]\) 均为 -,\([i+1,n]\) 中的前 \(c\) 个 - 变成 +。
P10197 [USACO24FEB] Minimum Sum of Maximums P
我们先对于边界加两个 \(+\infty\) 的数之后就是要最小化 \(\sum |a_i-a_{i+1}|\)。
需要调整一下最优子结构,对于两个端点 \(L,R\)(假设 \(L\le R\)) 之间的数,假设已经确定了所选数的集合,肯定将这些数升序排序,假设排出来的为 \(a_1,a_2..a_k\),代价为 \(|L-a_1|+|R-a_k|+a_k-a_1\)。可以发现 \(a_1\) 尽可能大,\(a_n\) 尽可能小的时候肯定是不劣的。
这个性质就很牛了,我们在同一段内填入的数字构成一个区间,否则用调整法把两个相交区间内的极值交换一下可以更优。因此可以进行区间 dp。

浙公网安备 33010602011771号