一个月封闭集训,可能题会稍微多一点。
001. P8940 [DTOI 2023] C. 不见故人 提高+/省选-
先来分析一下题目:写的还是比较简洁的,就是把区间里的数都变为它们的 \(\gcd\)。然后分析一下所谓代价,发现如果 \(k\) 小的时候会比较麻烦些,而 \(k\) 大起来反倒好办,就是直接全局改一下就好。
\(Key\space Observation\space 0\) 首先如果所有数都相等,那么答案一定为 \(0\)。
\(Key\space Observation\space 1\) 所有数最终都会变成全局的 \(\gcd\)。这就让我们可以对题目进行一步等价转化——将所有数除掉全局 \(\gcd\),最终所有的数就都可以变成 \(1\)。
\(Key\space Observation\space 2\) 每个数最多会被包含在一次修改中。这也是显然的,考虑反证:如果一个数被包含在两次修改中,那么为什么我们不将两次修改合并呢?这样改两次代价只会更大,与题目要求的最小性矛盾,故证毕。这就启发我们进行阶段性动态规划。
考虑动态规划,定义 \(f_i\) 代表前 \(i\) 个数字最少经过多少次修改才能全部变为 \(1\)。转移较为显然,即若 \(\gcd(a_{j+1},...,a_i)=1\),那么 \(f_i=\min(f_i,f_j+i-j+k)\),因为这个过程是倒着来的,所以我们也倒着枚举 \(j\) 方便快速递推 \(\gcd\)。
如若 \(n \leq 10^5\),那么可以考虑二分和 ST 表优化掉找左界这一步,但 \(n \leq 4\times 10^6\),导致我们我们必须想其他办法来优化这个动态规划。但这步启发我们可以把贡献拆为 \(f_j-j\) 和 \(i+k\) 两部分方便优化。
\(Key\space Observation\space 3\) 如若把 \(a_i=1\) 的 \(i\) 看做分界点,把序列分为 \(m\) 块,则在块的最右端点进行转移才是有意义的。这启发我们找到块,按照块进行动态规划。 Old Friend's Trick
找到块是简单的但细节较多,怎样处理看个人喜好。定义 \(g_i\) 为到第 \(i\) 块最少要经过多少次修改才能全部变为 \(1\)。则有两种转移方式,第一种是块内转移,\(g_i=g_{i-1}+r_i-l_i+1+k+[\gcd(a_{l_i},...,a_{r_i}) \neq 1]\),其中 \([p]\) 为艾弗森括号,\(p\) 是一个命题,若其为真,则 \([p]\) 为 \(1\),否则为 \(0\)。第二种是隔块转移,这样就保证在转移区间内。一定存在 \(j\) 使得 \(a_j=1\)。那么 \(g_i=\min\{g_j+r_i-l_{j+1}+k+1\}\),向上面一样拆贡献维护最小值即可,甚至不用单调队列。注意第二种转移的 \(j\) 要保证 \(j<i-1\),处理时有些细节。答案即为 \(g_m\)。
002. CF1292B Aroma's Search 1700
题目选自:2020, 2021 年 CF 简单题精选
题意就是它给你生成了一个点列 \((x_i,y_i)\)。这个点列是无穷的,给定了 \((x_0,y_0)\),生成方式是 \((x_i,y_i)=(a_x \times x_{i-1}+b_x,a_y \times y_{i-1} + b_y)\)。现在给定起点 \((x_s,y_s)\),询问 \(t\) 时间内最多能经过多少个点。
\(Key\space Observation\space 1\) 注意到 \(a_x\) 和 \(a_y\) 都是大于等于 \(2\) 的,所以在值域内的点其实是 \(\log V\) 量级的,这是一个非常非常关键的性质,它的出现让我们可以随便写都能过。
我们直接暴力生成这个点列,然后直接暴力枚举起点。这都是 \(\log V\) 带给我们的自信。
\(Key\space Observation\space 2\) \(x_i\) 和 \(y_i\) 是单调上升的,所以一个一个走的曼哈顿距离等价于直接走过去的曼哈顿距离。发现就好,证明是显然的。
然后在枚举起点的同时,直接暴力枚举左右端点,因为注意到,我们最多最多就只能折返一次,然后枚举是先向右走还是先向左走即可。时间复杂度 \(O(\log^3 V)\),做完了。
*003. CF1313D Happy New Year 2500
题目选自:2020, 2021 年 CF 简单题精选
题意很简洁,不需要我过多叙述。
\(Key\space Observation\space 0\) 数据范围 \(1\leq k \leq 8\)。看到这么小的数据范围我们应该在能力范围内可以想到以下算法:暴搜,状压,容斥。当然我们目前并没有任何头绪。
\(Key\space Observation\space 1\) 差分,我们注意到区间加在差分意义下就是单点加减,又注意到数据范围,\(n\) 在可接受范围内而 \(m\) 则不可接受,印证了我们这一猜想。
\(Key\space Observation\space 2\) 一维扫描线,值域广而有用的点不多应让我们想到了 【模板】扫描线。我们尝试借鉴扫描线的思路进行某种操作。Happy New Year's Trick
我们先把有用的 \(2\times n\) 个点加入一个数组里面。按照位置顺序排序,同时终点在前,起点在后,这是为了方便以后处理。并方便进行扫描线操作。并且存储时我们不妨给每个操作点一个权,起点为 \(i\),终点为 \(-i\),这方便我们下面来对接,也方便终点在前的排序。
接下来进入主体部分。利用 \(k\) 的性质,我们进行状态压缩动态规划。
我们定义第 \(i\) 到 \(i+1\) 这段区间为第 \(i\) 个操作段。\(i\) 在 \(1\) 到 \(2\times n\) 之间。特殊的,如果 \(i\) 的位置和 \(2\times n\) 一样,那么这个操作段的长度就为 \(0 \),表示整体操作结束。
\(Key\space Observation\space 3\) 我们定义 \(f_{i,j}\) 为第 \(i\) 到第 \(i+1\) 个操作段的 起点选择状态为 \(j\),\(j\) 是一个二进制串,因为保证了 \(1 \leq k \leq 8\),所以 \(j\) 最多有 \(8\) 位。但因为我们不知道你这些操作具体是什么,所以我们开一个 \(vis\) 数组记录这些对你这个区间有影响的操作编号。 Happy New Year's Trick 2
接下来考虑怎么转移,我们先记第 \(i\) 个操作段的长度为 \(len_i\)。
讨论第 \(i\) 个操作点是起点还是终点:
- 若第 \(i\) 个操作点为起点,那么我们考虑怎么刻画这个事情。因为我们不考虑你起始操作点的顺序,因为你已经存了编号,所以你随便找一个没有被占用的位置占用上即可,即把 \(vis_j\) 为 \(0\) 的一个点改为自己的 \(id\),并用这个点的位置去进行更新。想到这个后转移就较为容易,枚举二进制串 \(0\) 到 \(2^k\)。如果串 \(p\) 中存在 \(j\) 这一位,那么就是直接逆推,考虑前面没加上这一位时的答案,即 \(f_{i,p}=f_{i-1,p \oplus 2^j}+len_i \times (popcount(p) \bmod 2)\),如果没有 \(j\) 这一位,那么也是简单的,就是 \(f_{i,p}=f_{i-1,p}+len_i \times (popcount(p) \bmod 2)\)
- 若第 \(i\) 个操作点为终点,其实与起点是类似的。因为这是终点,所以前面必会有一个点对其有影响且是它对应的起点。我们找到这个点后,它就对这段区间没有了影响,把它取消标记,也就是将其 \(vis\) 值设置为 \(0\)。同时记录位置 \(j\)。那么转移也是非常简单的,如果串 \(p\) 中已经存在 \(j\) 这一位,那么就是不合法的,因为我们已经将第 \(j\) 位去除了,赋值为无穷大。否则 \(f_{i,p}= \max\{f_{i-1,p \oplus 2^j},f_{i-1,p}\}+len_i \times (popcount(p) \bmod 2)\)
最后一步是统计答案,就是 \(f_{2\times n,0}\),就是最后一个操作段已经不能被任何操作点所影响。注意一下初始值是全部 \(f\) 数组赋值为无穷大,而 \(f_{0,0}\) 单点为 \(0\)。所以这题我们就做完了。
004. CF1322B Present 2100
题目选自:2020, 2021 年 CF 简单题精选
题意简洁,不过多叙述。
非常妙的一道题。看到 \(a_i \leq 10^7\),我们可以考虑到二进制题目的一些经典操作。
\(Key\space Observation\space 1\) 按位统计答案,这看不出来的话你就没救了。
\(Key\space Observation\space 2\) 第 \(k\) 位是不是 \(1\) 只与前 \(k\) 位有关,与其他的无关,所以可以将每个 \(a_i \bmod 2^k\) 放到 \(b_i\) 里进行下一步操作。Present's Trick
\(Key\space Observation\space 3\) 第 \(k\) 位是否是 \(1\) 要分两种情况讨论,第一种是两数之和不进位到上面,那么两数之和应该在 \([2^k,2^{k+1})\),第二种是进位到上面,那么两数之和应该在 \([2^{k+1}+2^k,2^{k+2}-2]\) 内。
有了第 \(3\) 个关键发现,我们就可以排序双指针解决这个问题,那么第 \(k\) 位对其的贡献就是 \((cnt \bmod 2) \times 2^k\),最后都加起来即可。于是我们就做完了。
005. P5094 [USACO04OPEN] MooFest G 加强版 普及+/提高
题目摘自:我的《分治全家桶》博客
非常魔怔的一道题,反正分治就很人类智慧的呢。这题感觉树状数组做法是绿,分治做法得有蓝,因为不太好想。题目问的是 \(\sum \sum \max(v_i,v_j)\times |x_i-x_j|\)。
看到绝对值我们考虑先把绝对值拆掉再说。按照键值 \(x_i\) 从小到大排序。这样式子变成 \(\sum \sum \max(v_i,v_j)\times (x_j-x_i)\)。为什么要分治呢?因为前面那个非常讨厌的 \(\max(v_i,v_j)\)。
我们继续考虑后序遍历 CDQ 分治(或者说序列分治)。在递归计算完左右两个子区间的答案后,左右区间内部天然的 \(x\) 键值顺序就显得没那么重要了。我们考虑打乱它,因为在整体合并中,我们只需要知道 右区间的所有 \(x\) 值都比左区间的 \(x\) 值大 就够了。这一点与第一题的归并排序有异曲同工之妙。
考虑我们合并要干什么东西。我们要处理的是 左右两边的 \(v\) 键值 对除自己外另一半区间的影响。这启示我们按照 \(v\) 进行排序。
合并时,每次加一个左区间的 \(v\) 键值对右半区间的贡献显然是:
每次加一个右区间的 \(v\) 对左区间的贡献显然是:
注意边界情况也要加上这两种贡献哦!不然你就会输出 \(0\)。也有更简洁的写法,不过自认为我的这个比较清晰易懂。
006. P3403 跳楼机 提高+/省选-
同余最短路模板题,但是真的好高妙啊,谁能想到要这么做啊?
我们发现如果直接连边 SPFA 或者直接连边 Dijkstra 都会直接炸的死死的。我们只能另辟蹊径。
\(Key\space Observation\space 1\) 我们发现虽然 \(h\) 大的离谱,但是 \(x,y,z\) 的值域都在可接受范围内。启发我们对这三个值下手。
\(Key\space Observation\space 2\) 我们不妨对 \(x\) 进行考虑,如果一个数 \(k\) 可行,那么 \(k+px \leq h\) 的这一系列数都是很可行的。所以我们不妨找到一些很小的数,然后让他们加上这些 \(px\) 得到最终的个数。
\(Key\space Observation\space 3\) 考虑如何不重不漏的计数,我们寻找不变量,在加 \(x\) 的过程中什么不变呢?对了,就是模 \(x\) 的余数不变!于是我们考虑按照余数分类。按照上面说的,我们对模 \(x\) 的每一个余数找一个最小可行的数,把它不断加上 \(x\),计算不超过 \(h\) 的个数然后加起来即可。
\(Key\space Observation\space 4\) 如何实现这一点呢?我们考虑把模 \(x\) 完全剩余系中的 \(x\) 个数看做 \(x\) 个点,又因为我们还有 \(+y,+z\) 两种操作,所以我们连 同余边。将 \(i\) 连到 \((i+y) \bmod x\),边权为 \(y\)。\(+z\) 是同理的。这样我们跑一段最短路就可以找到模 \(x\) 的每一个余数的那个最小可行的数。Drop Tower's Trick
然后这道题我们就做完了。
007. P2365 任务安排弱化版 提高+/省选-
一眼能看出来是动态规划类型的题目。考虑朴素的动态规划算法。
这应该是简单的。定义 \(g_{i,j}\) 代表前 \(i\) 个工作,分了 \(j\) 组做的最小费用。转移应该是 \(g_{i,j}=g_{l-1,j-1}+(j \times s + sumt_i) \times (sumf_i-sumf_{l-1})\),只要你没读错题就能想到这个 \(O(n^3)\) 的动态规划。
\(Key\space Observation\space 1\) 本题最为神奇也是用到的唯一一个大 Trick —— 费用前置。在我们的推导中,我们发现复杂度的瓶颈在于分割的段数,但这又很难优化掉,因为我们需要知道花费了几倍的 \(s\)。但很遗憾,用普通的方法我们并不能解决或是规避这个问题。但是我们考虑一个 \(s\) 的影响,它能影响什么东西。很显然,这段时间可以影响在它后面的所有东西,又因为乘法是存在分配律的,所以我们就相当于把 \(s\) 这个费用前置,提前把它对后面的影响加上了,这样也就不必要知道具体有几个 \(s\) 了。费用前置 Trick/ Task Arranging Trick
008. [ABC379F] Buildings 2 1659 蓝牌题
感觉真的不好做啊?可能是我对这两种技巧都太生疏了吧。
你发现如果你单纯想预处理出所谓的“最左边的能看到你的”,那说明你跟我一样读错题了。因为题目中定义的“看见”这一动作是不完全具有单调性的。但这还是启示我们使用单调栈算法。
我们反过来考虑“你能看见啥”。那么我们考虑倒过来使用单调栈。
\(Key\space Observation\space 1\) 我们考虑信友队 NOIP 2024 联训 2024.11.13 的 T4 做法。在处理这样的多组询问问题时,我们考虑将询问离线。其实那题的做法也和这题差不太多。
我们考虑处理一组 \((l,r)\) 询问。考虑到我们从后往前使用单调栈,所以我们的单调栈实际上是一个单调递减栈。考虑楼啥时候能被看见。首先它的位置得大于 \(r\),其次它一定得比 \([l,r]\) 内的最大值要大,再次在 \([l,x]\) 中应该不存在比它高的建筑。
\(Key\space Observation\space 2\) 考虑在单调栈上二分。二分一个第一个位置比 \(r\) 大,那么从它一直到栈底的数都是可以的,那么这组询问的答案就是这段距离。
我们分别处理 \(m\) 组询问,这样我们这道题就做完了。
*009. [ABC379G] Count Grid 3-coloring 2304 黄牌题
打星是因为这题用到了一个状态压缩动态规划的经典优化 Trick——轮廓线状态压缩动态规划。
\(Key\space Observation\space 1\) 这其实是一个非常重要的发现,就是你发现如果 \(n\times m \leq 200\),那么 \(min(n,m) \leq \sqrt{n\times m}\) 约等于 \(14\)。这就直接把做法指向了状态压缩动态规划。但考虑到朴素的按行转移状态压缩动态规划的时间显然是承受不住的,于是我们把做法指向 ——> 轮廓线状态压缩动态规划
我们钦定 \(m<n\),并定义 \(f_{i,j,mask}\) 为填完前 \(i\) 行,在 \(i+1\) 行填完 \(j\) 个,左边 \(j-1\) 个和上一行的右边 \(j\) 到 \(m\) 组成的一个三进制状态 \(mask\) 的合法组成个数。(这显然是一个三进制状压动态规划,实际上其状态约只有 \(2^m\) 个,但也没有必要进行压缩因为时间上和空间上都可以通过)
首先我们先预处理出 \(f_{1,0,mask}\) 这是可以直接 \(O(m\times 3^m)\) 暴力枚举解决的。
考虑怎样转移,因为对于每一个格子,能影响它的只有上边和左边两个格子,先把这两个格子的状态按照某种方式快速求出来。然后枚举这一格的状态 \(0/1/2\)。然后你再以某种方式快速预处理出 \(newmask\),直接暴力转移即可。注意碰到边界要转移到下一行。
最后答案即为 \(\sum f_{n,0,mask}\)。注意状态要使用 unordered_map 存储,以达到卡常,节约时间,节约空间的目的。
010. [ABC377E] Permute K times 2 1685 蓝牌题
一道与置换有关的趣题。不是很难,但很有趣就是了。
首先考虑我们的熟知结论 Exercise G's Trick,如果轮换数组为一个排列,那么所有数都必然会在自己的置换环上行走。
考虑显然的先把所有的环搜出来,记录一下每个数字在哪个环上,环长,环上位置实际对应的数字,这都是简单的,可以一并记录。
\(Key\space Observation\space 1\) 倍增置换。考虑第一次置换后 \(i\) 位置的情况,记为 \(b_i\),则 \(b_i\) 显然等于 \(a_{a_i}\)。考虑第二次置换,设 \(i\) 位置上的数被置换到了 \(c_i\),则 \(c_i=b_{b_i}=a_{a_{a_{a_i}}}\)。于是置换 \(k\) 次走 \(2^k\) 步。 Permute K times' Trick
考虑到 \(k\) 非常大,于是我们可以使用快速幂,后面的问题就简单了。于是这题我们就做完了。
011. [ABC377G] Edit to Match 1782 蓝牌题
这场 G 是不是有点简单,这个字典树提示的其实有点明显了。首先数据范围给的是 \(|S|\),说明与总长度有关,联想到 Trie 的节点数也与总长度有关。所以每次插入一个串到字典树中。
然后肯定是前缀能取就取,后面的话就取一个最短的后缀。最短后缀就记一个数组,最后随便统计一下就做完了,这题是真水题了。
*012. P5999 [CEOI2016] kangaroo 省选/NOI-
连续段动态规划/ 插入类型动态规划/ kangaroo's Trick
非常强的一个动态规划技巧。常见的类型如有一个波浪形的限制,就像这道题一样。还有排列计数和不能重复用一些数这种限制。还有 \(n\) 最好在 \(n^3\) 到 \(n^2\) 范围内,这是比较可以接受的。
首先我们不考虑起点 \(s\) 和终点 \(t\) 的限制,只考虑求任意起点和任意终点的排列。
定义 \(f_{i,j}\) 为用了前 \(i\) 个数,连续段个数为 \(j\) 个的排列方案数。我们一般情况下的转移为:
- 增加一个连续段:\(f_{i,j}=f_{i,j}+f_{i-1,j-1} \times j\)。因为原先有 \(j-1\) 个连续段,所以有 \(j\) 个空格可以插入。
- 合并两个连续段:\(f_{i,j}=f_{i,j}+f_{i-1,j+1}\)。原先有 \(j+1\) 个连续段,有 \(j\) 个空格可以供插入合并。
需要注意的是,因为数组天然有序,所以我不管怎么合并都是满足条件的。
下面考虑 \(s\) 和 \(t\) 的限制。考虑如果 \(i=s\) 或 \(i=t\)。那么它们有两种转移。一个是插入到第一个/最后一个连续段的开头/末尾,一个是另起第一个连续段/最后一个连续段。就是 \(f_{i,j}=f_{i-1,j}+f_{i-1,j-1}\)。
同时前面的另起连续段部分也要变一变。因为在插入 \(s\) 和 \(t\) 之后我们就已经钦定了所谓的“第一个”和“最后一个”连续段。所以在 \(i\) 足够大的时候我们就不能往序列两边插入连续段了,这样会破坏起点和终点的性质。最后转移变为 \(f_{i,j}=f_{i,j}+f_{i-1,j-1} \times (j-[i>s]-[i>t])\),其中 \([p]\) 为艾弗森括号,\(p\) 为一个命题,若 \(p\) 为真,则 \([p]\) 为 \(1\),否则 \([p]\) 为 \(0\)。
然后这道题我们就做完了。
*013. P7967 [COCI2021-2022#2] Magneti 省选/NOI-
有了上一题的铺垫,我们这道题就是相对简单的。考虑 kangaroo's Trick,进行连续段动态规划。
考虑先按照 \(r_i\) 排序,方便转移。记 \(f_{i,j,k}\) 表示考虑了前 \(i\) 个磁铁,连续段个数为 \(j\),占用空间为 \(k\) 的方案个数。这题我们有三种转移(有一种是在这题可行,而在 kangaroo 那题是不合法的)
- 增加一个连续段:没啥好说的,就是 \(f_{i,j,k}=f_{i,j,k}+f_{i-1,j-1,k-1} \times j\)
- 合并两个连续段:就是 \(f_{i,j,k}=f_{i,j,k}+f_{i-1,j+1,k-2\times r_i + 1} \times j\),这也是简单的。
- 插入到已有连续段的两端:其实也不难,就是 \(f_{i,j,k}=f_{i,j,k}+f_{i-1,j,k-r_i} \times j \times 2\),\(\times 2\) 的原因是连续段的两边都可以插入。
最后答案就是 \(\sum f_{n,1,i} \times \binom{l-i+n}{n}\),后边那个组合系数是插板法得出的。
于是这道题我们就做完了。
014. P8865 [NOIP2022] 种花 普及+/提高
补了补 NOIP2022,发现第一题还是比较水的,就非常常规。
首先考虑 'C' 字型怎么做,非常简单,预处理出右边最长 \(0\) 串个数,记为 \(r_{i,j}\)。然后你考虑怎么构成这个 'C',其实就是下面随便选一行,随意选一个可行长度,然后你自己这一行随意选一个可行长度。你考虑由乘法分配律对后面可行的状态做一个后缀和,记为 \(sumc_{i,j}\),答案就是 \(\sum sumc_{i+2,j} \times r_{i,j}\)
然后考虑 'F' 字型怎么做,稍微难一点点,考虑预处理出下面最长 \(0\) 的个数,记为 \(d_{i,j}\)。然后你考虑 'F' 和 'C' 有啥关系。无非就是 'C' 下面拼了一行,'F' 下面拼了一个 'L' 型的东西,这是一样的,记一下 \(sumf_{i,j} = sumf_{i+1,j}+d_{i,j} \times r_{i,j}\),答案就是 \(\sum sumf_{i+2,j} \times r_{i,j}\)
然后我们就做完了,过于简单了。
*015. P3047 [USACO12FEB] Nearby Cows G 普及+/提高
历史遗留问题了属于是。大概是两年前剩的题了。现在依然不会,没有长进,恼!
考虑 \(f_{i,j}\) 为树上第 \(i\) 个点,距离为 \(j\) 的点权和。考虑到其实 \(f_{i,j}\) 是很不好转移的。所以我们设立另一个状态 \(g_{i,j}\),代表树上第 \(i\) 个点的子树内,与 \(i\) 距离为 \(j\) 的点的点权和。
首先我们可以一遍 DFS 求出 \(g_{i,j}\) 这是简单的。就是 \(g_{u,0} = a_u\),\(g_{u,i} = \sum g_{v,i-1}\)。
然后考虑怎样求出 \(f_{i,j}\),考虑子树内的贡献,就是 \(g_{i,j}\)。子树外的贡献我们使用容斥原理,就是 \(f_{u,i-1}-g_{v,i-2}\) ,我觉得这个式子非常困难,最好画图理解一下。也有可能是我容斥学的过于的不好了。
小优化:注意到我们可以重复利用 \(f_{i,j}\) 这个数组。但为了避免重复/缺少贡献,我们可以在换根的时候倒着枚举 \(k\) 来解决这一个问题。但其实不用这个优化也没什么事。