玉袍长剑堪风流,山川不念旧
April Solution Set
CF1805F Survival of the Weakest
先看 F1
难点在于存数。道理是存差分数组就好了。
然后最后答案再垒上一下系数。
然后差分数组每一位都不会变大。这是显而易见的。
现在考虑一下满数据范围的情况。
啊呀!实际上只用保留最小的 \(2 \log a\) 个数。
因为假设 \(a_1 + a_2 < a_n\) 则可以删去,否则 \(a_n\) 会在两步之内变成原来的一半,做完了。
CF1814F Communication Towers
考虑将点的限制转化为边的性质。
我想线段树分治,然后我就知道了每个时间点的一号节点的联通块。
但是打上标记是困难的。维护可撤销并查集,然后打懒标记就好了。
CF1797F Li Hua and Path
首先每次加入一个叶子,意味着好对子数不会变少。
其次,每次加入一个点,该点都是最大的,那么能够与其组成好对子的点一定不能是路径最小的点。
然后对子树容斥一下大概就是一个二维偏序。 \(O(n \log^2 n)\) 的复杂度,\(T\) 飞了。
啊啊啊,又是笛卡尔树,建完就做完了。
CF1815D XOR Counting
[北大集训 2021] 基因编辑
随便讨论一下,线段树维护一下就做完了。
[北大集训 2021] 魔塔 OL
在偏序限制下,我需要完成一个贪心的决策。
首先是回血怪,肯定比扣血怪先吃。
先吃消耗血量少的回血怪,再吃多的。
然后是扣血怪,则经推倒得到,按照治疗量从大到小排序吃。
将怪物按吃的顺序排序。
于是可以使用 bitset
计算高维偏序,知道哪些位置对当前询问贡献。
然后发现可以分块,按块处理询问,块的大小为 16
,可以做一遍 \(2^{16}\) 的预处理。
做完了,但要卡常。
CF1605F PalindORme
考虑如何检查一个序列是否合法。
每次可以拿出两个与当前 \(V\) 或起来相同的数配对,最后考虑能否完全配对。
这样是正确的,因为后选的不会因为先选的而不能选。
考虑容斥,对于每个方案,我们将在不断配对到不能配对的时候去选择它。
定义 \(f_{i,j}\) 表示考虑了 \(i\) 个数,他们或起来恰有 \(j\) 位有值。
注意到,为了容斥方便,我们要求 \(i\) 为奇数且不等于 \(n\) 时,中间那个数的位是其它数或起来的位的子集。
[北大集训 2021] 出题高手
考虑建立支配对。滚前缀和,然后每个数前面有用的数是连续递增的,因为随机,所以递增序列的长度会是 \(\log\) 级别的,维护出来然后对询问跑一个二维偏序即可。
[IOI2019]天桥
这题不简单。考虑 \(s=0\) 且 \(g = n - 1\) 的情况。
显然不会倒退的。然后有一个结论就是说,我一个天桥,只用从起点的下面上来,或者从终点的下面下去。
首先是起点,如果是从起点的上面上来,那不如等那个天桥走完了,从终点下来。
其次是终点,如果从终点往上面走,那不如在那个天桥的起点的时候就上去。
然后还有另一个结论,就是我们只用保存起点,终点和他们下面的第一个交点。
首先保存上面的交点是没有必要的。
其次是保存下面更远的交点也不必要。考虑起点下面更远的交点对应的天桥,如果我们在那个天桥上,那么我们可以提前走到更上面的天桥上去,使得在这个建筑的时刻我们恰好在下面第一个交点的天桥处。
然后是从终点下到更远的交点的天桥去,如果那个天桥更长,则先下到又短又高的,以后一定有机会再下一步,且不劣。如果那个天桥更短,则我们走又高又长的一点不劣。
然后就是不满足这个限制的了。这样的话就会有往回走的现象,往回走目的本质上是因为需要走上更厉害的天桥。那么只有跨过了起终点的天桥才可能这么厉害。于是我们把这样的天桥拆成三段,然后继续跑上面那个问题即可。
小 Y 和二叉树
先找到最小的,能放到第一个位置的节点。并以此为基石扩展。
因为之后的所有决策点都与子树有关,所以实际上我们以这个节点为根,做一遍预处理,就可以知道每个需要知道的子树的最小的第一个节点是多少,然后就可以开始决策。
值得注意的是,如果当前节点出去左儿子后只有一个度,那么需要讨论把那个出边当爹还是当儿子,如果第一个值一样大,那么那个值一定是出边对应的节点,此时当爹一定不劣。
避难所
我不明白。有一个很神奇的观察,当 \(b\) 较小时,合法解都可以是满足 \(a \ b \ b\) 的情况。
较大时,可以使用两个质数,去满足 \(a \ a \ a \ b^3\) 为较劣解,\(ab \ ab \ ab\) 较优的情况。
Hello world!
每个点最多被操作 \(6\) 次就不动了。
根号分治,如果步长大直接来。如果步长小,则对每个步长维护一个并查集和一棵树,用并查集压缩路径。用树和里面节点的 dfs
序和一个数据结构去维护前缀和。
注意到这个数据结构,\(O(1)\) 修改,\(O(\sqrt n)\) 查询的复杂度会比较好。
有一些细节,并且要写一个长剖跳 \(k\) 级祖先。
[ABC275Ex] Monster
先建大根笛卡尔树。
然后维护 \(f_{x,j}\) 表示以 \(x\) 为根的子树被减了 \(j\) 遍的情况下减完的最小花费。
可以发现的是,这个 \(f_x\) 是一个分段下凸一次函数。
于是我们可以差分一下斜率,使用可并堆维护这样一个 \(dp\) 的斜率变化点,并记录一下初始值即可。
「JOISC 2022 Day4」一流团子师傅
我可以先 shuffle
一下,然后二分找到 0
和 1
的分界点。
容易发现,这个分界点距离起点期望有小于 \(n \ln n\) 个元素。依次扫一遍可以得到一组解。
然后随着元素的不断减少,理应元素越来越少。
所以差不多能过。
「JOISC 2022 Day4」复兴计划
蛮难的。先要想到实际上每条边的控制范围是连续的,并且它在 \(x\) 的递增过程中会替代掉一条边。
于是按 \(w\) 从小到大排序,每次加边,然后破圈,那么就可以找到一条边会替代掉哪条边,并进而求出在 \(x\) 超过他们的平均数后,边被替代,于是就能求出每条边的控制区间。
后面的就随便做了。
「JOISC 2022 Day4」鱼 2
注意到,一条鱼不断向一个方向吃,只会遇到 \(\log\) 个需要决策的障碍,因为每次决策会使得它大小翻倍。
基于这一点,我们想到了区间合并,两个区间分别求出能吃完的鱼,然后我们其实只用花 \(\log V\) 的复杂度就可以求出是否能继续吃完整个区间。
当然还有本来吃了一个前/后缀,因为加了一个区间,所以能吃完的情况,这个也可以做,我们只用在前后缀维护一个可能遇到的障碍组成的栈,这个栈的元素个数显然也是 \(\log\) 级别的。那么吃完一个前缀,必然停留在这个障碍栈的元素上。
于是我们只用维护这个障碍栈的元素能否因为多加一个区间而吃完所有的鱼。这个可以双指针实现。
当然,区间合并,自然能够想到线段树维护。
复杂度 \(O(n \log^2 n)\) 。做完了。
CF1286D LCC
这题不难。我们只用把所有碰撞情况排个序,然后钦定撞哪一种,算概率即可。
算概率也比较好算,实际上就是一个动态 \(dp\) ,线段树维护即可。
CF618G Combining Slimes
考虑 \(a_{i,j}\) 表示使用 \(i\) 个格子,把最左边的数变成过一个 \(j\) 的概率。
同样,我们也可以计算出 \(A_{i,j}\) 表示使用 \(i\) 个格子,把最左边的数变成 \(j\) 且不被合掉的概率。
递推是平凡的。
因为任意两个数的最终状态,要么左边的是 \(1\) ,要么左边的大于右边的。
于是容易计算 \(f_{i,j}\) ,即使用 \(i\) 个格子,并且最左边的数最终为 \(j\) 的期望和,注意递推式要用条件概率。
经过实操,我们发现当 \(j = 50\) 时,其出现概率已经已经趋近于 \(0\),且 \(A_{n,j} \approx A_{50,j}\)。
于是就可以经过预处理后进行矩阵乘法。
「雅礼集训 2017 Day1」市场
很明显,难点是第二个操作。
但是每次 \(2\) 操作会带来巨大的变化量。我们不妨去分析他的复杂度。
考虑差分,每次区间修改会带来 \(2c\) 的差分总变化量。
每个位置的差分值只会变化 \(\log\) 次后就会变成 \(0\) ,对于全 \(0\) 段我们显然可以一个 \(\log\) 处理。
所以我们只要每次在处理一整个全 \(0\) 段那么我们的复杂度即正确。
总复杂度是 \(O(n \log^2 n)\) 的,但是有一个 \(\log\) 常数很小。
注意 \(\lfloor \frac{-1}{2}\rfloor = -1\),可能会对我们的复杂度产生爆炸式的干扰,我们就必须将判断条件变成区间内除以这个数后变化量相同,才能控制复杂度。
CF1804G Flow Control
如果做过 「雅礼集训 2017 Day1」市场 那么确实会很简单。
注意到一群用户,一起传输着传输着传输速度就会变成相近的,大概经过 \(\log\) 次传输速度除以 \(2\) 的操作。
那么,我们直接一段一段的维护,在一些时刻会发生用户传输的开始或者结束的事件,事件中间,我们手动迭代若干轮,知道他们的值变成一样的,这部分的复杂度是 \(\log^2 n\) 的。
考虑所有值都变成一样了该怎么做。哦,那就是存在明显的循环节,达到一个阈值就立刻除以 \(2\)。
不断算,找到循环节就一起做,就是了。因为最多做 \(\log\) 次就一定能找到循环节。
CF1188E Problem from Red Panda
不妨这么考虑,我们给每一个元素定一个操作次数,那么这个操作次数序列必然可以生成一个方案。
但应该不会是一一对应的。但是操作方案真不一定合法。
如果数的个数 \(x\) 出现次数大于 \(x + 1\) ,那么我们就只能进行 \(x\) 次操作。
不太会摸索性质了。首先有一个结论是,任意两个数的差值 \(\bmod k\) 不变。
确定一个数就可以确定其它所有数对 \(k\) 取模的结果。确定的数的取值不会影响总和余数是否合法。
?操作次数序列,只要能保证最小值为 \(0\),那么就一定能够生成一个不同的结果。
然后现在就是去检查这个 \(\{x\}\) 的可行性。为了使得它可行,则我们每次必然操作最小的还有操作的数字。每个数只要能被安全地操作过一次,就不会再变得不合法。
那么我们就先排个序。首先要求最后结果必须是非负的。在这种情形下,本身大于等于 \(k-1\) 的数实际上就必然在过程中合法。而小于 \(k - 1\) 的数,则考虑在他前面的有值的数的个数是否不超过它。
即 \(a_i \geq i - 1\)。若 \(a_i < i - 1\) 且 \(s > a_i\) ,则此时必然不合法,否则则一定合法。
插板法计算一下合法状态即可。
CF1023G Pisces
首先,这是一个求解最小链划分的问题,根据 Dilworth 定理,可以转化成最长反链。
那么考虑树形 dp
。一个方案合法,等价于,首先子树内部为反链,且存在时刻 \(T\) ,没有一条鱼可以在时间 \(T\) 到达根节点,也没有一条鱼可以在 \(T\) 时刻从根节点出发到达任何一条鱼。
即 \(\forall\ \ i \ \ d_{i} + dep_v > T,d_i - dep_v < T\)。
注意到,这个 \(T\) 的存在范围是一段区间,其实就是若干个开区间交起来。
另外参数乘以 \(2\) 可以保证有整数解。
用 map
维护差分值。从儿子上来,本质上就是取 max
。那么就是差分的正值左移,负值右移。
打标记,然后开个优先队列维护可能相遇的位置就好。
贺了一份代码。
[AHOI2022] 钥匙
按照颜色考虑。
让钥匙和宝箱形成二元组产生贡献,使得这样的二元组内,按照后捡到的钥匙开最新的宝箱的顺序。并统计全部的贡献对,然后就是二维数点。
好写好写,但不如吃饭。
倍增写错给我干破防了。
[APIO2016] 烟火表演
[ABC275Ex] Monster 的配套练习!
野蛮的想法是,定义 \(f_{x,j}\) 表示以 \(x\) 为根的子树,所有点到当前点的路径长度为 \(j\) 的最小花费。
转移式写一下:
若 \(w(u , v) > j - k\)
若 \(w(u,v) \leq j - k\)
其中 \(f_{x}\) 是一个分段一次函数。然后 \(f_{v,k} + k\) 也是。
这道题最妙的地方,在于将最小值附近分界,开始讨论。
令 \(f(x)\) 表示原函数,\(g(x)\) 表示加上头顶那条边后的新函数。
边长为 \(l\) ,\([L,R]\) 是最小值所在的区间。
容易发现最小值左边的斜率均小于 \(-1\)。
对于这个函数的变换是优美的,相当于将 \(\leq L\) 的部分向上平移 \(l\) 单位,将 \([L,R]\) 部分向右平移 \(l\) 单位,在 \([L,L+l]\) 中间插入一条斜率为 \(-1\) 的直线,并将 \(> R + l\) 的部分的斜率改为 \(1\)。
这个发现每条斜率都是若干条斜率为 \(-1\) 的直线累起来得到的。所以我们仅仅只用记录所有拐点,并在加起来的时候合并所有拐点且不去重,就可以得到全部的信息。
同时,右边加起来也是一样的,都是若干个斜率为 \(1\) 的直线加起来。转移的话我们只用弹出儿子个数减去 \(1\) 个拐点就可以了。
[PA2009]Circular Game
有连续的三颗同种颜色的球则只要没被封死则立于不败之地。
两颗同种颜色的球被封了就是真死了。
所以策略就是,如果有三颗同种颜色的球,并且中间有空隙,那这个颜色先天不败。
如果两个都先天不败那就平局。否则就先天不败者胜。
否则如果三颗同种颜色的球被封死了,那封死的那两个球就必然不会再动了,否则别人就活了。整体可以看做一个障碍球,这就直接破环为链了,有很多条链。
每个棋子向同色方向走是不优的,因为相当于在自断生路,对方也可以还原出上一个状态。
所以如果存在两种相同颜色靠在一起,则可以继续断开考虑。
如果白黑交替出现,首尾不同色,则是经典 nim
模型。
如果首尾同色,则只要没填死,对全局的影响就是必不败。
然后就是没有三个颜色相同的情况,两个颜色靠在一起,我想还是可以拆开。
现在就是全局黑白交替出现了。
这个可以永续发展,讨论完了!
[APIO2018] 选圆圈
先按半径排序,然后实际上就是依次判断每个点是否被前面的圆所包含。
一个性质是,我需要检查的圆之间是没有交集的,也就是说在一定空间内,圆的数量可能是常数级别的。
根据这个性质,我们将整个平面划分成 \(2R \times 2R\) 的格子,其中,我们只装 \(r > \frac{R}{2}\) 的删除圆进去。
这样一个格子内最多只有 \(4\) 个删除圆。
以此类推,我们将平面划分成 \(R \times R\) 的格子,其中,我们只装 \(\frac{R}{2} \geq r > \frac{R}{4}\) 的圆进去。
这样一来,通过倍增划分,我们就成功将需要检查的圆是数目大规模缩小,复杂度变成 \(O(n \log^2 n)\) 的。
「HAOI2018」字串覆盖
如果 \(r - l > 50\) ,可以后缀排序,找到出现区间,然后主席树上二分,找到所有出现位置统计答案。
然后是 \(r - l \leq 50\) ,可以对于每个位置和长度维护一个与其前缀相同的前缀和。查询先找到区间内最小的,然后找到区间右边最小的,作差。
做完了。
[IOI2009] Regions
强制在线使得这个问题显得相当麻烦。
嗷嗷嗷,可以集合大小阈值分治。
如果 \(A + B \leq \sqrt n\),则可以将 \(A\) 中所有覆盖区间找到,排序,然后把 \(A,B\) 归并起来,就可以在 \(O(A+B)\) 的时间复杂度内找到答案。
如果 \(A > \sqrt n\),则这样的 \(A\) 只有 \(\sqrt n\) 个,可以考虑去处理他到其他所有颜色的解,直接从根往下扫一遍即可。
如果 \(B > \sqrt n\),则这样的 \(B\) 只有 \(\sqrt n\) 个,可以把他们拍成 dfs
序,然后滚一遍前缀和,扫一遍其他所有颜色就可以得到其他所有颜色到 \(B\) 的答案。
那么我们就在时空复杂度为 \(O(n \sqrt n)\) 的开销下解决了本问题。
[CTSC2018]暴力写挂
减去另一棵树 LCA
的深度就离谱啊,就离谱。
可以将答案转化一下:
然后对第一棵树点分治,然后在第二棵树上建虚树 dp
即可统计答案。
Menci 的序列
从后往前考虑,对于每一个 \(+\) ,费用提前计算考虑。
即定义 \(f_{i,j,k}\) 表示考虑到第 \(i\) 个位置,选择了 \(j\) 个 \(*\) ,最高的位的状态是 \(k\)。
写一个 bitset
高精,可以拿到前 \(30pts\)。
其次继续观察,当出现连续的大于 \(2\) 个 \(+\) ,则我们可以删去 \(2\) 个,然后在前一个乘号的前面一个位置加上一个 \(+\),容易发现是等价的,于是当前序列的长度是 \(O(k)\) 的。
对于一段 \(*\) ,发现除了最后一段都可以至少选择一个 \(*\)。可以考虑调整法。
首先在序列最前段添加并选择 \(*\) 不会对答案产生影响。
从后往前调整,如果这一段没有,如果到上一个乘号之间没有 \(+\),则直接替换。
如果有偶数个,先替换,再把 \(+\) 减半。
如果有奇数个,先替换,再把 \(+\) 减半向下或向上取整,在它后面的乘号之间增加或者减少一个 \(+\)。
可以发现一定可以做到。
这样一来我们上面的 \(k\) 只有 \(0/1/2\) 的情况,因为连续的 \(+\) 也只有两个,每次遇到 \(*\) 也必然除以 \(2\)。
此外,存在方案使得满足每段选择一个 \(*\) 的情况下,每段 \(+\) 也至少选择一个。
首先最后一段一定要选择一个 \(+\) ,因为上一段选了 \(*\) 之后,当前值变成偶数。
如果一段没有 \(+\) ,如果后面选了过量 \(*\) 则不影响答案。
如果后面没进位,则我选了一定更优。
如果有进位,那么我直接把到进位那一片的 \(+\) 全部不选,这里选一个,答案不变。
最优方案满足,最早的出现连续两个 \(*\) 的位置,其后全选。
证明就是,如果后面一段没有全选,则这一段少选一个,下一段多选一个,之间如果有两个 \(+\) 则减少一个,否则两个 \(*\) 足以隔断影响,后面不会进位,答案不劣。
于是可以枚举哪一段第一次出现两个 \(*\) ,如果该位置前后要选择的 \(*\) 不小于 \(k - 1\),前面的位必须凑成全 \(1\)。否则前面全选,因为不可能过限。
考虑第一次前后不小于 \(k - 1\) 的时刻。