2025.1.1 近期练习
新年好,各位。
P7054 [NWRRC2015] Graph
我们假设 \(k=0\),那么我们求最小字典序就是通过一个小根堆维护当前入度为 \(0\) 的点,每次取出最小。
那么如果 \(k\neq 0\),我们就可以阻止“取出最小”这个过程,也就是给当前最小这个点一个入边。
我们重复给当前最小点一个入边的操作可以贪心地使最小字典序最大。
但是入边是从哪里连过来的很难处理。我们考虑先不给这个点分配入边。
直到必须要用到上述点的时候,我们让其连向拓扑序的上一位即可。所以我们用大根堆维护这些点。
所以当小根堆大小 \(\ge 2\) 的时候,取出最小的并将其加入大根堆。
注意小根堆大小为 \(1\) 的时候就无法给其分配入边了,否则一定成环。
不过我们可以考虑取出一个大根堆里的元素出来,连上上一个,并让当前小根堆的元素连向他。
若小根堆大小为 \(0\),那么我们就要取出大根堆的元素,每次弹堆顶即可。
AGC001F Wide Swap
首先考虑令 \(Q=P^{-1}\),即 \(P_{Q_i}=i\),\(Q_i\) 相当于是在排列 \(P\) 中 \(i\) 的位置。
那么交换的条件就相当于交换相邻的 \(Q_{i},Q_{i+1}\),且他们差的绝对值 \(\ge K\),转化为熟悉的模型。
我们要想 \(P\) 的字典序最小,就相当于将 \(Q\) 排序。
注意到,重复交换 \(Q_{i}-Q_{i+1}\ge K\) 的两位置过后,每次都是使其更优,重复操作直到 \(Q_i-Q_{i+1}<K\)。
注意,我们去除了绝对值,相当于每次都减少了一个逆序对,与排序的本质相同。
那么最后 \(Q\) 相当于排好序。并且有且仅有一种排好序的方式,此时是最优的。可以关注 \(K=1\) 的情况。
证明有且仅有一种最优,假设存在另一种,那么肯定存在某对数相对位置改变,
而操作可逆,所以这两种状态是可达的,那么这对数是可以调换顺序的,所以只有一种最优。
排序的话,如果是 \(O(n^2)\) 那么可以直接类比冒泡排序。优化考虑换成归并排序。
相当于合并两段数,这两段数都满足 \(Q_i-Q_{i+1}<K\),并且各自内部顺序不会再改变。
我们只需要解决当前数是填左边的还是右边的问题。如果右边更优那么其就得交换到左边这里来。
设左边是第 \(i\) 个数,右边是第 \(j\) 个数,\(R_j\) 要交换到 \(L_i\) 前面,那么 \(L_{i,i+1\dots mid}-R_j\ge K\)。
所以我们只需要求一个后缀最小值即可。于是归并排序完成。
联想到排序确实很厉害。但是有没有简单的办法呢?
考虑相对位置不变的数,他们之间连有向边,跑拓扑排序即可。用线段树优化建图。像 AGC010E。
P4785 [BalticOI 2016 Day2] 交换
首先可以看出这是一棵二叉树。我们取出根左右三个点,并在这里进行决策。
注意到当且仅当右儿子是三个点中最小的点的时候,我们需要做出选择。选择左右儿子的顺序。
那么我们就形成子任务的形式。设 \(f_{u,i}\) 表示 \(u\) 子树,根是 \(i\) 的答案。
注意到状态数是 \(O(n\log n)\),因为一个 \(f_{u,i}\) 必须满足 \(i\) 是祖先,而二叉树深度是 \(O(\log n)\)。
现在涉及到两个方案的比较。
我们设 \(L<R\),那么现在比较 \(f_{ls,L},f_{rs,R}\) 与 \(f_{ls,R},f_{rs,L}\) 两种方案。
不难发现只需要比较 \(f_{ls,L},f_{rs,L}\) 两个方案中 \(L\) 的位置哪个更浅即可,如果一样浅就是 \(f_{ls,L}\) 方案更优。
所以我们只需要维护所有 \(f_{u,i}\) 表示 \(i\) 作用的位置。考虑用 map 存储。复杂度 \(O(n\log^2 n)\)。
为了卡常我们不需要求出所有的 \(f_{u,i}\),而是在要用的时候再求出,并记忆化。
CF1383C String Transformation 2
图论建模,我们可以把 \(a_i\to b_i\) 连边。那么只有 \(20^2\) 种位置,对应每种边。
那么我们的操作就是将若干个起点相同的边转移起点,终点不变。目标是只有自环。
我们可以得到一个答案的上限就是 38,考虑将所有 \(a\) 并到一起,然后在分到每个 \(b\)。
我们可以得到一个贪心的结论,就是“分”这个过程每个 \(b\) 只能有一次。
也就是说必须所有 \(a\to b\) 都并起来之后才能操作为 \(b\to b\),即形成自环。
考虑优化答案,考虑先取出每个连通块,不同连通块之间不需要有并或分的操作。
其实还可以优化“分”,我们不需要并到最后再“分”,我们可以在并的过程中将其留下来不并上去。
考虑“并”是一个树形结构。我们考虑找到一棵最大的树或森林使得不存在祖先连到后代的边。
因为如果存在这样的边,那么肯定还要分下来。否则就不需分下来,答案可以减去“分”这些点的代价。
考虑用状压来求这个最大的树。准确来说是 DAG。
AGC004F Namori
神仙题。
我们先考虑树,因为树是二分图,我们考虑黑白染色。那么每次操作相当于调换相邻黑白的位置。
那么目标是黑色与白色的位置互换。所以如果黑色个数不等于白色那么就不能做到。
考虑拆贡献到每条边。假设一棵子树内有 \(a\) 个白色,\(b\) 个黑色,那么这条边要走 \(|a-b|\) 次。
考虑将白色设为 \(1\),黑色设为 \(-1\),dp 求出子树和 \(dp_u\),那么答案是 \(\sum|dp_u|\),前提是 \(dp_{rt}=0\)。
那么考虑偶环基环树。偶环基环树也是二分图我们也可以黑白染色。
考虑取出环上每个点的 \(dp_u\),对应其子树要进/出多少个白色点。
给环边定一个正负的方向,那么 \((i, i+1)\) 这条边的“流量”是 \(x_i\),那么 \(dp_i=
x_{i-1}-x_i\)。
我们即最小化 \(\sum |x|\),注意到 \(x\) 的相对差是不变的,只能平移,直接平移到中位数 \(=0\) 即可。
考虑奇环基环树,考虑先扣掉一条边染色,然后多的这条边两端颜色相同。
每次操作是将两个黑点变成白点或反之。那么这条边的作用相当于调整使得黑白颜色相同。
设环上所有的 \(dp\) 的和为 \(sum\),那么让环上每个 \(dp\) 都减去 \(sum/2\),若 \(sum\) 是奇数是无解。
注意此时不存在“环流”,所以不能平移。
CF1610H Squid Game
有点意思。考虑树是一条链的情况,那么这就是 CSP2024T2 的贪心模型。
考虑按祖先点的深度给链排序,如果当前链没有被覆盖,那么就在祖先点深度 \(+1\) 位置操作一次。
考虑通解。注意到对于所有非祖先-后代链,我们只需要在 \(1\) 操作一次即可覆盖他们所有。
那么我们只考虑祖先-后代链。模仿树是链的情况,考虑按照祖先的深度排序。
同理,如果当前链没有被覆盖,那么那么就在祖先点深度 \(+1\) 位置操作一次。
考虑判断是否被覆盖,设这条链是 \(u\to v\),\(dep_u+1\) 的位置是 \(w\),那么 \(subtree(w)-subtree(v)\) 有点。
考虑用数据结构维护。
最后再判断是否将所有非祖先-后代链都覆盖了,没有就去 \(1\) 操作一次。
CF1270G Subset with Zero Sum
神仙题。
考虑 \(i-n\le a_i\le i-1\),移项得 \(1\le i-a_i\le n\),这是否让我们想到 \(i-a_i\) 对应 \(1\sim n\) 的一个数?
考虑图论建模,令 \(i\to i-a_i\) 连一条边。那么如果形成环的话相当于 \(\sum i=\sum i-a_i\),那么 \(\sum a_i=0\)。
这是一个 \(n\) 个点的内向基环树,势必有环。
AGC030E Less than 3
考虑如何描述这个 \(01\) 模型。考虑用 \(0,1\) 之间的分界线描述这个问题。
注意到,我们每次操作是:将某个分界线左右移动;在最边上加入一条分界线或移出一条分界线。
我们相当于将两个串的分界线匹配上。因为分界线不能从中间删,所以匹配一定是连续的区间。
考虑枚举他们是如何匹配的,注意不一定其中一个要完全匹配,只有 \(O(n)\) 种情况。
对于匹配了的分界线,贡献是位置差的绝对值即距离;没匹配的就是从最边上加入或删掉。这是下限。
根据直觉我们不需要关注构造方案,总之一定会存在一种方案的答案达到我们求的下限。
那么我们便完成了此题。注意分界线有 \(01\),\(10\) 之分,所以我们要考虑奇偶性的问题。
P7831 [CCO2021] Travelling Merchant
设 \(dp_u\) 表示到 \(u\) 后要想无限走的代价。\(dp_u=\min_v(\max(dp_v-p_{u,v},r_{u,v}))\),考虑拓扑排序处理。
由于这是不是一个 DAG,我们做 dp 会有后效性的问题。
那么我们能不能将这张图变为 DAG?考虑一个环,取出最大的边 \((u,v)\),那么 \(dp_u=r_{u,v}\)。
因为 \(p\) 始终不降,所以但凡走过了 \((u,v)\) 这条边后面一定能走回来,且后面的边一定都能走。
那么我们就删掉这条最大的边。相当于去环路,形成 DAG,就进行拓扑排序。
同时我们也获得了一个初值可以进行 dp,否则我们就会面临回溯依赖等后效性问题。
P5912 [POI2004] JAS
即求最深叶子最浅的点分树。但是这个太难处理,考虑转化问题。
即给每个点标号,代表其在点分树上的深度,对于两个标号相同的点之间路径一定存在标号更大的点。
目标是最大的标号最小。
考虑贪心,我们以子树形式划分子任务,从下往上填,可以证明填最小能填的一定最优。
假设我们填 \(v\),那么需要保证子树中存在的 \(v\) 与其路径上必须存在更大的点;
而且对于在不同子树的两个标号相同的点,如果其中没有更大的点,那么 \(v\) 必须比他们更大。
所以简单维护一下子树内还不存在祖先比他大的值有哪些。显然只有 \(O(\log n)\) 种值。
子树形式的贪心很常见,相当于在 LCA 处处理路径。
P4364 [九省联考 2018] IIIDX
相当于给树的每个点分配值,使得构成小根堆的性质。使得这棵树的 BFS 序形成的字典序最大。
一个显然的贪心,就是从前往后逐位确定,每次选最大能选的。
我们考虑逐层处理。显然每个子树的值都对应一个区间,然后形成子树形式的子任务。
我们每次拿出最大的区间出来分配给第一个子树;其中这个区间的左端点对应子树根的值。
但是,这个算法只适用于 \(d_i\) 不同的情况。主要问题是,我们当前只关注分配区间的左端点的值。
所以我们只需要保证我们选的这个值最大且预留 \(siz-1\) 个比他大的即可,这就是子任务。
最大的值的个数如果大于 \(1\),我们考虑将所有这个最大数都包括进分配给该子树区间。
因为这样的话可以使得后面的选择更多,且当前选择不会更劣。
具体如何找这个最大的值呢?我们考虑维护 \(f_i\) 表示 \(\ge i\) 的数的个数。
因为 \(f\) 是单调的,我们考虑线段树二分找到我们该选的值,然后将这个数后 \(siz_i-1\) 个位置“预订”掉。
然后更新 \(f\),相当于进行前缀减操作。
我们可以 \(1\sim n\) 枚举逐位确定,然后每到一个点就接触其父亲的“预订”操作。
P7078 [CSP-S2020] 贪吃蛇
我们考虑先模拟一遍整个过程来确定每条蛇的决策。一条蛇如果选择吃,那么它之后一定不被吃。
已知只有两条蛇的时候决策一定是吃,相当于递归的边界。
那么如果一条蛇在模拟的过程中被吃了,且它之前是有选择的权利的,那么它就会选择取消那次决策。
但是我们并不知道这条蛇到底会不会被吃,如果吃了它的蛇之后也会被吃,那么它就不会被吃。
假设 \(D\) 吃会被 \(C\) 吃;\(C\) 吃会被 \(B\) 吃;\(B\) 吃会被 \(A\) 吃。若 \(A\) 吃,那么 \(B\) 不吃,\(C\) 吃,\(D\) 不吃。
那么就可以形成一个方便处理的“逻辑链”。
注意到,若一条蛇吃了之后不会变成最小的,那么其之后一定也不会变为最小的。所以这也是一定吃。
即:\(a<b<c<d\),那么 \(c-b<d-a\)。
因为我们只关注第一个不吃;而如果 \(C\) 吃了会被 \(B\) 吃,无论 \(B\) 吃是否会被 \(A\) 吃,就一定出现不吃。
而通过上面那个性质我们发现所有逻辑链都存在于连续的区间。我们只需要找第一个就行了。
也就是说,我们要找第一个 \(C\) 使得 \(C\) 吃使得 \(C\) 变为最小。
具体地,整个过程分为两个阶段:
阶段一:所有最强蛇进食后都不是最弱蛇,放心大胆吃!
阶段二:所有最强蛇进食后都是最弱蛇,直到有条蛇可以放心吃为止。
我们不用 set,只需要用两个队列存储还没吃的,以及已经吃过了的,这两个队列分别内部满足单调。
AGC032E Modulo Pairing
我的评价是什么几把玩意儿。
考虑没有模 \(m\) 的条件,那么就是正数第 \(i\) 个跟倒数第 \(i\) 个匹配起来,很显然这样是最优的。
考虑加上模 \(m\) 的条件,注意到,所有匹配形成的区间要么包含要么不交。
可以证明:若 \(a<b<c<d\),形成 \((a,c),(b,d)\) 这样的匹配一定是不优。
若 \(a+c<m,b+d\ge m\),换成 \((a,b),(c,d)\) 这样匹配更优。
若 \(a+c,b+d\) 二者都 \(<m\) 或 \(\ge m\),那么换成 \((a,d),(b,c)\) 这样匹配更优。
所以我们猜测,匹配分为前后两个部分,每个部分各自是诸如正数 \(i\) 和倒数 \(i\) 匹配。
其中前面部分满足匹配加起来 \(<m\),后面部分的匹配加起来 \(\ge m\)。考虑枚举切点做到 \(O(n^2)\)。
考虑如何优化这个过程,注意到前面部分即使 \(\ge m\) 答案不会更劣,而后面部分越往左答案越小。
所以我们二分后面部分最左的位置,使得其匹配的最小值满足 \(\ge m\) 即不会更劣。
P9293 [ROI 2018] Addition without carry
纯折磨。先考虑如何判断一个 \(\sum b_i\) 合法,从高到低考虑每个二进制位 \(x\) 和当前最大的 \(a_i\)。
如果 \(|a_i|<x\) 那么用 \(2^x\) 直接消掉 \(a_i\)。如果 \(|a_i|=x\) 那么得到 \(a_i-2^x\)。否则无解。
不断维护这个过程即可完成判定。
设最高位为 \(D\),注意到,D 的取值范围是 \(\max\{|a_i|+i\}-1\) 或 \(\max\{|a_i|+i\}\)。考虑搜索。
先判定 \(D=\max\{|a_i|+i-1\}\) 是否合法,设该最大值在 \(x\) 处首次取到,那么 \(a_1\sim a_{x-1}\) 直接删除,\(a_x\) 删掉最高位,然后递归限定最高位 \(<D\) 的子问题。
对于 \(D=\max\{|a_i|+i\}\) 的情况,直接删除所有 \(a_{1}\sim a_x\) 然后递归。
注意我们在递归时是有最高位限制的,即钦定 \(D\) 不超过某个值,这就可能导致一个子状态无解,但全局状态肯定有解。
注意到当前的每个 \(a_i\) 都是初始 \(a_i\) 的一段后缀,对每个 \(a_i\) 的所有后缀排序。
线段树维护 \(\max |a_i|+i-1\) 是容易的,set 维护 \(a_i\) 排序后的结果,根据上述过程模拟即可。
根据势能分析,递归的次数是线性级别。我也是蒙蔽了。
AGC014D Black and White Tree
先手获胜的条件是存在一个白点其所有相邻的点也都是白点。
注意到叶子节点是很特殊的,设这个叶子是 \(u\),那么若先手选了 \(u\) 或 \(fa_u\),后手都必须选另一个。
注意到,如果一个点的邻居有两个及以上的叶子节点,那么先手选这个点就获胜了。
那么如果不存在这样的点呢?那么所有叶子都拥有不重复的父亲,而贪心地, \(fa_u\) 一定是白点。
因为 \(fa_u\) 不可能作为中心白点出现了,其如果做贡献肯定是作为中心点相邻的点;
而相邻的白点的作用跟没有这条边等价,不妨直接删掉 \(u,fa_u\) 两个点,形成删叶子形式的子任务。
现在我们判定的条件相当于找是否存在度数为 \(0\) 的点。做一个拓扑排序即可。
P7407 [JOI 2021 Final] ロボット (Robot)
以边做点。我们要走一条边的代价有两种:一是直接改变其本身的颜色;二是改掉所有同色的边。
设 \(s_{u,c}\) 表示 \(\sum_{(u,v,c)}p_{u,v}\)。那么一条边的代价是 \(\min(p_{u,v},s_{u,c}-p_{u,v})\)。这是算多余的。有后效性的问题。
假设从 \(x\to y\) 到 \(y\to z\),若 \(x\to y\) 用的是第一种代价,那么 \(y\to z\) 如果用第二种代价就算多了。
事实上,此时的代价是只用算 \(y\to z\) 的代价就好了。然后以边做点的话考虑优化建图,建个虚点即可。
CF1784C Monsters (hard version)
一个比较显然的贪心就是我们如果后用操作 \(1\) 不如前用,最后尽可能多使用操作 \(2\)。
所以我们会先使用操作 \(1\) 使得血量变为诸如 \(1,2,3,4\dots n\) 的形式,一个数的个数可以有多个。
如果没有多余的 \(a_i\),答案是 \(\sum a_i-\frac{1}{2}n(n+1)\)。
考虑如何判定哪些是多余的,贪心地,每个数只有一个是最优的。
从小到大枚举 \(a_i\),并给其赋为不超过其本身的,并且当前没有这个数的最小值。如果赋不了就是多余。
考虑如何处理前缀的答案,我们每次新加入一个 \(a_i\)。若当前 \(\le a_i\) 已经有 \(a_i\) 个了就多余。
否则就可以加入进去,同时我们将原本加入但此时多余的删掉。考虑找第一个满足 \(cnt_{1\sim i}> i\) 的数。
线段树维护 \(cnt_{1\sim i}-i\) 的最大值即可。关于求位置 \(\ge p\) 第一个 \(\le 0\) 的数这个线段树二分我竟然不会。
这个不是单侧递归,但是相当于拆成 \(O(\log n)\) 个区间其中只有一个会递归下去。
P5021 [NOIP2018 提高组] 赛道修建
这个贪心很典再拿出来看看。
显然二分,然后贪心使得匹配数最大化,考虑当前儿子伸上来了若干条链如何最大匹配。
我们除了匹配最大化还需要使得伸上去链的最长。显然我们需要使得匹配出来的长度最接近 \(mid\) 越好。
有两种贪心策略,第一种是从小到大,每次选最小的且能匹配的;第二种是从大到小。
第一种策略是对的,因为我们需要保证最大匹配的情况下,最后贡献的是最大的未匹配的链,
然而第二种策略不会增加最多匹配的对数反而可能会减少最大的伸出去的长度。
这是一个匹配模型的贪心,AGC032E 也是,都是大的对小的这样。
更严谨的证明:设当前我们最小的元素是 \(a_x\),其找到最小的 \(\ge mid\) 匹配是 \(a_y\)。
考虑最优匹配中若包含 \(a_x\),选择 \(a_y\) 是最具“决策包容性”的。
若最优匹配中不包含 \(a_x\),那么 \(a_y\) 必被包含,设 \(a_y\) 匹配 \(a_z\),显然 \(a_z\ge a_x\),所以选 \(a_x\) 等效或更优。
P8170 [eJOI2021] Waterfront
显然二分+贪心。贪心的话考虑能砍就砍,如果当前能砍但之后都不会超出限制就不砍。
我们考虑把复杂度做到 \(O(mk\log V)\),首先我们可以算出总共要减多少次,如果超过 \(mk\) 一定无解。
之后对于每个时间我们不能判断每个能否砍,而是提前算好其下一个能砍的时间。用 vector 维护即可。
P3826 [NOI2017] 蔬菜
首先考虑一次询问怎么做。我们有一个贪心策略:
每次选出当前能选的最贵的菜,取最后一天使得我们可以在这天卖这种菜,并在这天卖掉。
维护这个东西可以考虑堆处理,并查集维护下一个可以用的天。复杂度 \(O(\sum pm\log n)\)。
我们尝试将前 \(p\) 天的答案转化为 \(pm\) 次卖的机会,即使我们卖的时间 \(>p\) 也可以转化为 \(\le p\) 的。
考虑维护 \(N=10^5\) 天的答案,用我们上面的贪心策略,\(p\) 的答案是前 \(pm\) 次的前缀和。
复杂度 \(O(Nm\log n)\)。
关于贪心的证明可以考虑将整个过程倒过来,那么这样的话每次能卖的菜的集合是不缩的。
在时光倒流的时候,我们选择变多,所以选择不会有后效性,那么选最贵的菜在最后一天买是对的。
其实我们的贪心策略跟从后往前选每次选最贵的若干个等价,我们只是将最贵的几个分配给哪些天。