好题选写

P2476 [SCOI2008] 着色方案

很好的绿 dp,场上卡我 2h,场上只考虑了二进制状压,然后组合数填数,最后发现没法去除重复情况。

说一下简单的正解,这题组合数也是能搞的,只是需要多开一维记录当前存在多少相邻的同色位置。考虑到 \(c_i \leq 5\),我们记录每种颜色个数的个数,然后按照个数记忆化爆搜,这样一想就很简单了。具体地,我们令 \(f(a,b,c,d,e,las)\) 表示 1 - 5 每种颜色还有多少,上一次是在哪一类数量选的,因为一个数字用完之后会扔到下一维,防止两种相同颜色在一起,所以要减掉 \(1\)

code

P2051 [AHOI2009] 中国象棋

以为是挺简单的 dp,结果场上没写出来。

发现每一列/行只有三种情况,有 0/1/2 个棋子,假如我们枚举行,限定行的放置个数,就只需要考虑列的情况了,具体地,我们只需要维护前 \(i-1\) 列的放置情况即可。令 \(f(i,j,k)\) 表示前 \(i\) 行,\(j\) 列有一个棋子,\(k\) 列有两个棋子,另外没有棋子的列可以表示为 \(m-j-k\),可以直接考虑转移。

当新拓展的一行不放棋子的时候: $f(i+1,j,k) = f(i,j,k) $,

当新的一行放一个棋子的时候,分类讨论是增加哪种列:

\(f(i+1,j+1,k) = f(i,j,k)\times (m-j-k)\)

\(f(i+1,j-1,k+1) = f(i,j,k) \times j\)

放三个棋子的时候同理讨论转移:

\(f(i+1,j+2,k) = f(i,j,k) \times \binom{m-j-k}{2}\)

\(f(i+1,j,k+1) = f(i,j,k) \times j \times (m-j-k)\)

\(f(i+1,j-2,k+2) = f(i,j,k) \times \binom{j}{2}\)

注意取模,统计答案即可。

code

P5304 [GXOI/GZOI2019] 旅行者

之前集训的时候就听过的 trick,没想到这场考了,竟然还是不会,我是菜b。

首先,我们暴力枚举再最短路的复杂度是 \(O(k(n+m)\log m)\)。显然是爆炸的,我们考虑二进制分组,我们每次划分两个集合 \(\left \{A\right \}\)\(\left \{ B \right \}\),按位枚举关键点,这一位为 \(1\) 的扔到 \(A\) 集合,否则扔到 \(B\) 集合,再对一个集合连超级源点,跑一遍最短路即可。会发现,假如答案的左右端点不同,那么他们至少有一位是不同,所以会有至少一次被跑最短路,答案是正确的,复杂度 \(O(Tn\log n\log k)\),虽然还有 \(O(Tn\log n)\) 的做法,但这种 trick 倒是很神奇的。

注意两个集合需要互相跑最短路,要建两个图。

code

AT_dp_q Flowers

场上还以为能评个蓝,一开始看到有人 10min 过了,吓一跳,疯狂写,然后假了一次 (。

考虑设计 \(f(i)\) 表示以 \(i\) 位置结尾的上升子序列的最大价值和,转移是 \(f(i) = \max_{a_j < a_i} \left \{ f(j)\right \} + h_i\),然后就可以用线段树维护 \(f\) 数组的前缀 \(\max\) 和单点修改即可,这种东西卡了我 40min,菜死了菜死了菜死了菜死了。

code

P6273 [eJOI2017] 魔法

上场卡死我的唐题,对标 2023CSP-S T2,真唐完了,今年不会 CSP 还是个 2= (。

场上考虑了类似消消乐那题的答案合并,发现两个答案区间并起来也是合法的,所以我们可以用类似消消乐的方式跳链表维护,但显然这样是假的,随便构造一组类似 baaaaaaaa... 的样例就卡爆了。

正解,注意到,令 \(s_{i,k} = \sum_{j=1}^{i} [S_i = k]\),则我们判断一个区间合法的标志就是 \(\forall a \in T,pre_{r,a}-pre_{l-1,a} = k\),此处 \(k\) 是一个正整数,换句话说,假设我们固定一个字母用来表示 \(k\),则这个式子写作 \(pre_{r,a}-pre_{l-1,a} = pre_{r,b}-pre_{l-1,b}\),把式子移项,得到,\(pre_{r,a}-pre_{r,b} = pre_{l-1,a}-pre_{l-1,b}\) 那么我们只需要记录每个位置每个字母和这个固定字母的差值,哈希一下扔进 map 里面,记个数即可。

code

P10083 [GDKOI2024 提高组] 不休陀螺

很好的体现了场上没用草稿纸的问题,考虑分析区间合法的充要条件。

  • 区间的总和不能为负,即 \(\sum_{i=l}^{r} (b_i-a_i) \ge 0\),记作一式

  • 任意时刻,我们都必须能打出下一张牌,最坏情况就是把所有负收益的放在前面打出来,即 \(E - \sum_{i=l}^{r} \max \left \{ 0,a_i-b_i\right \} \ge \max_{i=l}^{r} a_i[a_i<b_i]\)

  • 进一步考虑,在上述情况下,我们还要考虑负收益的牌这时候能不能打出来,最坏情况肯定就是它是最后一张负收益的牌,然后判断是否合法,即 \(E-\sum_{i=l}^{r} \max \left \{ 0,a_i-b_i\right \} \ge \max_{i=l}^{r} b_i[a_i > b_i]\)

整理一下后两个式子,写作 \(E-\sum_{i=l}^{r} \max \left \{ 0,a_i-b_i\right \} \ge \max_{i=l}^{r} \min \left \{ a_i,b_i\right \}\),记作二式。

则我们需要同时满足两个式子,分别讨论:

  • 对于第一个式子,令 \(pre_i = \sum_{j=1}^{i} (b_i-a_i)\),则第一个式子为 \(pre_r-pre_{l-1} \ge 0\),即 \(pre_r \ge pre_{l-1}\)

  • 对于第二个式子,可以发现,左边式子单调不增,右边式子单调不减,存在这样的性质,我们可以直接双指针维护。

具体地,我们可以拿 ST 表维护二式右边式子,然后每次双指针拓展到最远的 \(r\) 点,用 BIT 离散化前缀和后计数即可。

code

P10930 异象石

两个多月之前做的了,题挺好的,写一下。

现在我们要在树上支持三个操作。加特殊点,抹除特殊点,求特殊点路径覆盖边的长度。这种东西看起来挺难搞的,那我们就一步一步模拟着来看。

人类直觉做法,发现所有情况可以分类讨论,假定当前点为 \(u\),我们肯定是要找他前后最优的点来计算路径,这里是可以直接二分的,可能会发现有情况满足取的不会是最近的点,但是这种是不影响结果的,放到了下面去讲。

只有前驱或者只有后继的情况没什么好讨论的,我们考虑一般情况。

拟定当前的根有三棵子树,每棵子树下挂着一堆点,令 \(u\) 在中间子树的某个节点上,他下面也有子节点。

image

画出图类似这样,我们记根的三棵子树分别为 \(x\)\(y\)\(z\),特别地,我们令 \(y\) 子树内 \(x\) 往上到根的部分记作 \(v\),往下的部分记作 \(w\)

此处以增加为例,删除同理,讨论三个点的位置关系,前驱为 \(pre\),后继为 \(nxt\)

  • \(pre\)\(x\) 子树内,\(nxt\)\(w\) 内,就不画图了,抽象理解一下,我们需要加上 \(dis(pre,x)+dis(x,nxt)-dis(pre,nxt)\)

  • \(pre\)\(x\) 子树内,\(nxt\)\(z\) 子树内,我们需要加上 \(dis(pre,x)+dis(x,nxt)-dis(pre,nxt)-dis(x,root)\),此处 \(root\) 为根节点。

  • \(pre\)\(v\) 内,\(nxt\)\(w\) 内,同情况一。

  • \(pre\)\(v\) 内,\(nxt\)\(z\) 内,我们需要加上 \(dis(pre,x)\)

我们将每种情况的加减都变为 \(dis(pre,x)+dis(x,nxt)-dis(pre,nxt)\),情况一三不变,情况二少了 \(x\) 到根的答案,情况四多算了 \(dis(x,pre)\),发现这两种情况下我们如果加上 \(dis(pre,nxt)\),答案就是正确答案的二倍,进一步拓展这个结论,通过拼接几种情况,我们最后需要加上 dfn 最小的到 dfn 最大的路径长度就能使得所有边正好被经过两次,这个可以自己去手动模拟一下,这种结论挺抽象的,可以类似环尾再走到环首。

还有上面说到取的不是最近点的问题,就是类似情况三的时候,子树 \(z\) 上还有点距离 \(x\) 更近,发现此时我们按照上述方法更新的话增加这个点和没有增加的路径长度是一样的,所以说对答案并不会产生影响。

set 维护特殊点,二分 dfn 序再算答案就好了,复杂度 \(O(n\log n)\)

补:过了 P3320 这个结论应该会更好理解一些,就是走过去再从终点走回来,每条边都被覆盖两次,再除掉就好了。

code

P4137 Rmq Problem / mex

回滚莫队基础练习题,可是我们不会回滚莫队,考虑另类做法。

使用回滚的原因,是 \(\operatorname{mex}\) 不好进行添加操作,复杂度容易退化,我们考虑一种方式维护增加,首先很好想的就是权值线段树上二分找前缀 \(1\) 的和是否等于区间长度,这个实现起来也比较简单,可是复杂度是 \(O(n\sqrt n \log n + n\log ^2 n)\),对于 \(2e5\) 的数据是无法通过的。

在思考,发现这种算法的瓶颈还是在于添加的 \(\log\),我们考虑使用添加 \(O(1)\) 的数据结构再维护,没错,还是分块。我们考虑对于值域再次分块,这样就可以直接进行修改了,查询时也不需要二分,直接便利每一个块看哪一个块中有空缺的与元素就好了,这样复杂度是 \(O(n\sqrt n + n\sqrt V)\) 的,其中 \(V\) 是值域,可以通过,而且跑得飞快。

code

[ABC298E] Unfair Sugoroku

开学之后感觉就开始颓了,做点简单题。

概率 dp,我不会的东西,考虑大力 dp,设 \(f(i,j)\) 表示 A 已经有 i,B 已经有 j 的概率。

直接大力转移:\(f(i+\min\left \{n,i+x\right \},j+\min\left \{n,j+y\right \}) += f(i,j) \times \frac{1}{p\times q}\)

复杂度 \(O(n^2pq)\),完全能过。

code

P8025 [ONTAK2015] Związek Harcerstwa Bajtockiego

感觉好久没更新了,最近做题好慢啊,写简单题。

题意就是每次树上俩点走 \(k\) 步走到哪,不能到达就输出最远的点。

这东西显然是可以大力树剖的,发现转折点是一个关键的点,前半段上升,后半段下降,分类讨论就好了。记 \(dep_u\) 表示节点 \(u\) 的深度,那么从 \(u\) 点走到 \(lca\) 的步数就是 \(dep_u - dep_{lca}\),下降同理。

  • \(dep_u-dep_{lca} < k\) 的时候,说明一定走不到,我们直接考虑跳链,一条链的链头记作 \(tx\),当 \(dep_{u}-dep_{tx} < k\) 的时候,说明这一条链都不符合答案,继续跳即可,反之则答案就在这条链里面,答案是显然具有单调性的,直接在链上二分即可,这里就是树剖的性质,一条链上的节点编号是连续的。

  • \(dep_u - dep_{lca} = k\) 的时候,答案就是 \(lca\)

  • 否则,答案就在下降路径上,上升路径的步数是固定的,直接减去即可,然后类似上升过程中的跳链做法,判断步数在最后一条链上二分找答案即可。

复杂度 \(O(n\log^2n)\)

code

P1654 OSU!

不会期望,所以刷点水期望 dp。

发现这个连续段不好搞,转成一个一个增加来看,假如有原答案 \(x\),我们新增加一个 \(1\),那么答案的变化是 \((x+1)^3 = x^3+3\times x^2 + 3\times x +1\),相对于原答案增加 \(3\times x^2 + 3\times x +1\),然后对 \(x\)\(x^2\)\(x^3\) 分别 dp 就好了。

code

CF1997E Level Up

差点场切的 *2200,一个月前做的,补一下题解,虽然也是一个多月前写的(。

写一下 E 吧,毕竟想了我好久。不会正解 BIT,写暴力根号分治,也就比正解慢了 1000ms 而已(?)。

首先,我们能够想到一种暴力,我们离线下来询问,依次按照 \(i\)\(x\) 排序,然后我们对于每一种 \(i\) 处理答案,对于 \(i\) 的种类少的数据跑得很快,但是这种做法容易被 \(i\) 多的极端数据卡到 \(n^2\)。然后,我们再考虑优化一点的方案,对于每次够了 \(k\) 次,我们都可以二分出这个位置,查询区间内可以用主席树做到 \(\log^2\),看着感觉复杂度挺对的,可是对于一堆 \(i\) 很小的数据,我们的二分可能会退化,也有缺陷。考虑将这两种算法结合,对于 \(i \leq V\) 的查询,我们可以用第一种方案暴力算,其中 \(V\) 是阀值,然后这样由于阀值的限制,第一种的复杂度就可以控制在 \(O(\sqrt n)\),然后 \(i > V\) 的用第二种算,还是因为阀值的限制,复杂度可以得到控制,这样做总体的复杂度是 \(O(n\sqrt n + n \log^3 n)\) 的,会发现,被卡了 (。

考虑哪里还能再优化,发现当 \(i>V\) 的时候,我们的等级最多会升到 \(\frac{n}{V}\),这就解决了前缀和爆数组的问题,简单算一下,\(V\) 取到 \(n\log n\) 的时候最好,数组开到 \(2e5\times 120\) 就够了,然后先按照每个数算一遍前缀和,再记一个前缀和扫另一个前缀和,相当于扫了一个二维平面吧(?),就可以做到 \(O(1)\) 的获取区间答案了,少了两只 \(\log\),复杂度 \(O(Vn+n\sqrt {n\log n} + \frac{nm}{V}\log n)\),跑了 1400+ ms,挺好的。

code

P5648 Mivik的神力

感觉是很神奇的题,首先会有一个跳 \(\max\) 的思路,可是这样显然会被类似单调递增序列卡掉,我们考虑如何优化。

发现这种跳的过程我们很类似的会用在倍增求 LCA 中,我们往哪方面想,假如我们将一步跳跃看做树上一跳向上的边,那么我们最后的答案就是求树上前缀距离了,这就很简单了。

神奇的,code

P5789 [TJOI2017] 可乐(数据加强版)

口胡成功力。

先不管它是怎么走的,先考虑一个点可能怎么走来,显然就是和它相连的点的方案数相加对吧,写成转移方程,令 \(f_i\) 表示第 \(i\) 个点的方案数,那么每增加一秒,他的答案就变成 \(f_i = f_i + f_{j_1} + f_{j_2} +...\),这种东西就很像数列加速的样子对吧,所以我们直接写成矩阵,\((i,j)\)\(1\) 当且仅当他们之间有边,由于 \(n,m\leq 100\),所以这样做是对的。

再考虑,还有自爆和不动的情况,自爆我们可以连一个点到外面,然后让那个点连自环,然后再让每个点跟自己相连,就处理了不动和自爆的情况,就完了。

code

[ABC332F] Random Update Query

简单期望,期望做的还是少,还在考虑和后面有一点捆绑关系。

考虑当一个区间新增时,对于一个在区间内的值 \(val\) 的修改就是令他变为 \(val \times \frac{r-l}{r-l+1} + k \times \frac{1}{r-l+1}\),然后还有区间修改,那么这就是一棵区间乘,区间加,单点查询的线段树了。

code

P3979 遥远的国度

经典 tick 树剖换根,虽然树剖没有办法直接换根,但是我们可以分类讨论解决。

简单分析可得,记当前点为 \(u\),根为 \(root\),我们以 \(1\) 号点建树剖,假如 \(u\)\(1-root\) 路径上,那么换根对树剖就是有影响的,此时的答案区间就是不包含 \(root\) 所在的 \(u\) 子树区间,取个补集就好了,其他情况没变化,注意特判 \(root\)\(u\) 重合的情况。

code

[ABC371F] Takahashi in Narrow Road

好题。为什么好?因为我不会。

首先能够想到每次向左向右会使得一整块棋子向目标点右边偏移,这就需要我们二分处理,是可以做的,处理之后,区间推平,再覆盖一个等差序列就好了,这样做是可以的,但是麻烦,考虑简单做法。

第一次见这个 trick,考虑令 \(a_i \gets a_i - i\),那么我们推平等差数列的操作就变成区间赋值了,然后我们二分向右找是 2log 的,这里选择线段树上二分,虽然这也是第一次学,但是也是简单的,其实线段树上二分就和普通二分差不多,还是要求单调性,每次的决策扔到线段树节点上就好了,跟普通二分的决策方式是一样的,可是线段树二分的复杂度是 \(O(n\log n)\) 的,总复杂度 \(O(n+q\log n)\)

code

P8817 [CSP-S 2022] 假期计划

赛前写写真题了属于是,还是挺简单的。

由于每段路程都是分开的,只有选点不能重复这一个条件,我们可以把所有点走 \(k+1\) 步能走到的点都找出来,复杂度是 \(O(n^2)\),用 vector 存下来,发现这样就可以把图缩小,每条边连接的点代表他们之间的距离小于等于 \(k\),这与原图是显然等价的。我们现在只需要考虑选点不重复这一个难题了,猜想复杂度肯定是 \(O(n^2)\),发现可以枚举中间两个点 \(BC\),然后 \(A\) 的取点范围就是 \(1\)\(B\) 取点的交集,\(D\) 的取点就是 \(C\)\(1\) 取点的交集,取交集这一步也可是 \(O(n^2)\) 完成,然后直接贪心就好了,我们直接拿点集最大值进行匹配,当有重复点时,取次大点即可,可是发现可能 \(B\) 的次大值也与 \(C\) 重复,所以还要取一个次次大值,然后拿两边的 \(3\) 个值互相比较就做完了,复杂度 \(O(n^2)\)

一发过,好耶。

code

P11253 [GDKOI2023 普及组] 小学生数学题

开颓,不会绿。

感觉很抽象啊,注意到 \(i!\) 可以 \(O(n)\) 预处理,并且 \((ij)^{-k} = i^{-k} \times j^{-k}\),所以,这东西可以欧拉筛筛出来,因为质数个数约为 \(\frac{n}{\log n }\),所以复杂度是 \(O(n)\),要卡一卡常。

code

CF809C Find a car

模拟赛场上会了单点求值的办法,没想到前缀求和,无敌了。

一种不用注意力的方法,通过打出 \(30 \times 30\) 的表来可以发现这是个分形的东西,一大个大矩形的长度为 \(2^k\),那么他就可以被分为边长为 \(2^k\)\(2^{k-1}\)\(2^{k}\)\(2^{k-1}\) 的四个小矩形,而且可以观察得出大矩形的每一行都是一个 \(2^k\) 的排列,你甚至还可以得出他的分形规律,但对正解没什么用。

每次询问一个矩形的答案,自然可以差分变成询问四个左上角在 \((1,1)\) 的小矩形的答案,现在就要考虑怎么求一个覆盖左上角的矩形的答案。

由于这个东西的特性,我们先将他向外补到 \(2^k\),然后讨论他右下角点的位置:

像这样,假设他在右下角,那么蓝色部分就是一个完整的边长为 \(2^{k-1}\) 的矩形,通过之前观察发现他的每一行都是 \([1,2^{k-1}]\) 的排列,所以可以直接求和,对于红色部分的两块多余部分,我们发现看他们的长边,都是 \([2^{k-1}+1,2^k]\) 的排列,他们也可以直接算出来,然后还剩下最后的绿色部分,由于我们前面补出的是一个正方形,所以绿色部分肯定在主对角线上,所以他左上角的值是 \(1\),这样就可以继续向下递归求解这个小矩形的答案。

类似的,假设这个点在左下角或者右上角,那么他就只剩向左/上的红色部分,这个跟上面一致是可以算出来的,然后在递归小矩形的答案,但是由于他不在主对角线上,所以他的值会有一定偏移量,其实就是 \(2^{k-1}\),递归时也要加上。

假设这个点在左上角,就是当前区间取大了,还可以取到更小的正好包含这个点的区间,所以令 \(k\) 缩小即可。

代码很好写,注意取模。

submission

P1169 [ZJOI2007] 棋盘制作

跟上一场模拟赛 T4 一样的 trick 啊,悬线法 dp,解决二维 dp 中的某些求矩形的问题。

以本题为例,我们要求一个最大的 01 交错的矩形和正方形,直接做肯定是不好搞的,包括什么二分答案之类的,因为你不好写 check,而暴力 check 是 \(O(n^2)\) 的,肯定爆炸。

考虑一种不一样的 dp 方式,定义 \(f_{i,j}\) 表示点 \((i,j)\) 向左最多到哪里是 01 交错的,同理 \(g_{i,j}\) 表示向右的,然后定义 \(up_{i,j}\) 表示这个点向上最多扩展几个,考虑如何转移。

显然,因为我们要找的是一个矩形,边长肯定是一定的,按照一段上下连续的 10 序列来看,要求这个序列中最小的 \(g\) 和最大的 \(f\),就可以当作横边长了,然后连续段长就是纵高,就可以很好的维护面积了。

具体来说,先横向处理,当当前位置和前一个位置颜色不同时,

\[f_{i,j} = f_{i,j-1} \]

\[g_{i,j} = g_{i,j+1} \]

之后,纵向处理,当这个点与上一行的颜色不同时,

\[f_{i,j} = \max \left\{ f_{i,j},f_{i-1,j}\right \} \]

\[g_{i,j} = \min \left\{ g_{i,j},g_{i-1,j}\right \} \]

\[up_{i,j} = up_{i-1,j}+1 \]

代码很好写,code

posted @ 2024-08-23 21:04  Wei_Han  阅读(13)  评论(0编辑  收藏  举报