Codeforces Data Structures *3000 乱写

开个坑。大致会持续更新。

所有题搬自 nzhtl1477 的课件。

好多都是老题,可能真正难度到不了 *3000。

或许可以说是套路数据结构大全。

斜体标注的题目是相对水一点 / 没什么意义的题,大多分布在前面(大概)。

CF526F Pudding Monsters (*3000)

这题居然已经降到蓝了..

upd. 又评回紫了,不知道什么情况。

经典套路了,对于每一个右端点,维护 \((\max - \min) - (r - l)\) 的最小值与最小值数量,因为这东西肯定 \(\ge 0\),所以当最小值为 \(0\) 的时候,最小值的个数就是答案。

CF464E The Classic Problem (*3000)

很无脑的一题。

乍一看最短路板子,但是值域是 \(2^{10^5}\) 的。

但是完全可以直接套 Dijkstra。我们考虑 Dijkstra 需要哪些操作:某个数+边权,比较大小。

因为边权都是 \(2^x\) 形式的,我们拿线段树来维护这个数。那么加上一个 \(2^x\) 就相当于将从 \(x\) 开始的一段极长连续 \(1\) 变为 \(0\),再在后面的一个 \(0\) 变为 \(1\)。这个直接在线段树上二分即可。

然后是比较,可以维护一个哈希值,然后在线段树上二分 LCP 就行了。

由于对于每一个点都需要记录,线段树可持久化一下就行了。

CF603E Pastoral Oddities (*3000)

每个点的度数都是奇数这个限制很奇怪,我们找一个等价条件。

首先不难想到如果度数都是奇数,那么连通块大小不可能为奇数。

由此可以发现,如果一个连通块的点数为偶数,那么一定可以从这个连通块中找出一个边集,使得度数都为奇数。

证明考虑找出一颗生成树,然后从叶子开始往上删,如果这个点的度数为偶数就删除它与父亲的连边,这样如果除了根节点的度数都是奇数,根节点的度数一定也是奇数。

生成树与最大值最小可以启发我们去考虑维护一颗最小生成森林。维护直接使用 LCT 即可。

加边就是正常的加,然后需要维护一下每个连通块的大小,这个需要维护 LCT 上的子树信息,只需要在 LCT 上维护虚子树的和即可。

然后每加完一条边,我们尝试从大往小去删边,一直删到出现连通块大小为奇数的结束。删掉最后这一条边之后没有了合法方案,说明这条边一定存在于选择边集的方案里,而我们是从大到小删的边,说明这条边就是边集里最大的那条边,于是这条边的边权便是答案。

太久没写过 LCT 了,这东西调了一下午,呃呃。

CF1446D2 Frequency Problem (Hard Version) (*3000)

首先有一个结论:原序列的众数一定是答案区间的众数。

考虑从原区间不断删数,这样删到某一时刻会出现一个数与原众数的出现次数相等,因此原众数一定是答案区间的众数。

设原区间的众数为 \(x\)

考虑再进行一步题意转化:我们改成求最长的一个区间,满足 \(x\)\(y\) 的出现次数相等。

为什么这个与原问题等价呢?同样的道理,如果 \(x\) 不是区间的众数,那么一定可以继续扩大区间使得 \(x\) 成为区间的众数,所以将 \(x\) 是区间的众数这个条件删掉不会使答案变优。

那么有一种朴素的想法:枚举 \(y\),然后寻找出现次数相同的最长区间。

那么我们可以将 \(x\) 看作 \(1\)\(y\) 看作 \(-1\),那么出现次数相等相当于区间和为 \(0\),也就是两个前缀和相等。那么我们对于每一种前缀和维护最大值与最小值,那么答案就是最大的 \(\max\)\(\min\)

这个复杂度是 \(O(nV)\) 的,肯定会炸。考虑到总数为 \(10^5\),那么根号分治。

对于出现次数大于 \(\sqrt{n}\) 的,就直接用上面的做法。

对于出现次数小于等于 \(\sqrt{n}\) 的,考虑枚举出现次数 \(t\),那么我们相当于需要让所有数的出现次数都小于等于 \(t\) 且区间长度最长,那么可以双指针进行计算。

CF150E Freezing with Style (*3000)

考虑二分中位数 \(x\),然后 \(\ge x\) 的设为 \(1\)\(< x\) 的设为 \(-1\)

这样假如某个路径的中位数大于等于 \(x\),那么路径的权值和肯定 \(\ge 0\)

那么我们就相当于要找出是否存在一条路径,使得它的长度在 \([L,R]\) 之间且权值和 \(\ge 0\)

考虑点分治,由于我们只需要考虑是否存在,那么我们只需要存权值和的最大值。

那么我们对于每个长度,拿线段树维护这个长度的权值和的最大值,然后每插入一条路径的时候看权值和是否大于等于 \(0\) 即可。

CF997E Good Subsegments (*3000)

这题和 Pudding Monster 一模一样,只不过加了一个区间查询。

直接把询问挂在右端点上,每次查询改成区间查询即可。

CF319E Ping-Pong (*3000)

考虑两个区间之间会如何连边。

如果两个区间相交,那么它们连双向边。

如果两个区间是包含关系,那么会从小区间往大区间连。

并且发现区间长度递增,那么新加的区间只可能是与之前的区间相交或包含某些区间,不可能被某个区间包含。

那么我们可以用并查集将连双向边的点缩起来。此时需要维护现在区间的左端点与右端点跨过了哪些区间,直接扔线段树上维护然后暴力合并即可。

查询的时候,如果两个区间属于同一个连通块那肯定可以,否则可能是连单向边,也就是第一个区间包含于第二个区间,那么维护一下每个连通块的左右端点即可。

CF696E ...Wait for it... (*3000)

这题为啥能评到 3000...?

每次找一条路径删除前 \(k\) 小的点,考虑每次找到最小的点然后删除,树剖+线段树就能 \(\log^2\),每个点最多被删一次,总复杂度就是 \(O(n \log^2 n)\) 的。

子树加就打个标记就完了。可以标记永久化一下,这样比较好写。

CF1163F Indecisive Taxi Fee (*3000)

有趣题。

首先分情况讨论:我们先跑出一条 \(1 \to n\) 的最短路。

如果修改的边比原来权值小,且在最短路上,那么答案肯定是这条最短路减去少了的权值。

如果修改的边比原来权值小,但不在最短路上,那么最后最短路要不然就是原最短路,要不然就是 \(1 \to u \to v \to n\)\(1 \to v \to u \to n\)

如果修改的边比原来权值大,且不在最短路上,那答案就是原最短路。

重点看最后一种情况。首先最后答案要不然就是原最短路,要不然就是一条不经过这条边的最短路。因为如果不是原最短路且经过了这条边,那么选原最短路一定更优(不劣)

我们钦定这条路径经过某条不在原最短路上的路径 \(u \to v\),那么这条路径肯定是 \(1 \to u \to v \to n\)。而我们发现,\(1 \to u\) 一定与 \(1 \to n\) 有一个公共前缀(可以考虑最短路树/dijkstra的过程),同样 \(v \to n\)\(1 \to n\) 有一段公共的后缀,那么如果删除的边在这两者之间,这条路径就可能是答案。

那么我们预处理出前缀与后缀,在线段树上区间取 \(\min\) 即可。

CF436F Banners (*3000)

分块凸包,套路题。

link

CF793F Julia the snail (*3000)

考虑对每一个右端点维护左端点答案。

考虑某一条绳子 \((l_i, r_i)\),那么对于所有值 \(\ge l_i\),他们都可以被修改为 \(r_i\)。直接扔吉司机线段树即可。

CF1178G The Awesomest Vertex (*3000)

分块凸包,套路题。

link

CF773E Blog Post Rating (*3000)

首先考虑最优答案,考虑交换相邻两个逆序对,发现答案一定不劣,所以最优的答案肯定是 \(a\) 从小到大排序。

然后考虑这东西长什么样,发现一定是先有一段不断降低,然后当 \(a_i = -i\) 的时候开始单调不降。

我们首先找到这个 \(a_i = -i\) 的点,拿权值线段树维护 \(a_i + i\),然后线段树上二分即可。

然后是后面的单调不降部分,可以写出来式子,发现答案就是 \(\min{a_n, a_{n - 1} + 1, a_{n - 2} + 2, \cdots}\)。于是再拿一颗线段树维护 \(a_{n - i} + i\),然后区间 \(\min\) 即可。

CF331D3 Escaping on Beaveractor (*3000)

题其实很简单,考虑按照经过的线段数进行倍增,然后记录经过这么多线段需要花多少时间,然后倍增处理即可。

需要处理出来沿着每个线段一直走能够到达哪个线段或到达边界,这个可以跑四遍扫面线实现。

想想都难写,看题解两篇都写了 8KB 代码,我觉得我还是算了吧。

CF185E Soap Time! - 2 (*3000)

考虑我们现在找出了一个点,那么答案就是所有点到这个点的时间的最大值,我们要让这个最大值最小,不难想到二分答案。

那么我们首先二分一个答案 \(mid\)。那么,如果我们把每个点在 \(mid\) 时间内能经过的点看作一个集合,那么 \(mid\) 合法当且仅当每个点代表的集合的交集不为空。

考虑一个点能够到达的点集是一个菱形。这个菱形很丑,所以我们先考虑将曼哈顿距离转成切比雪夫距离,这样能到达的点集就是一个矩形了。

然后我们现在要考虑的就是这几个集合是否有交。首先每个点可以走以自己为中心的 \(mid\) 的一个矩形,而且它可以走到离它最近的一个车站,设这个最短距离为 \(dis\),那么这样就相当于又以每个车站为中心的 \(mid - dis\) 的一个矩形,某一个点能够到达的点集就是这几个矩形的交。

下面有两种方式能够得出一个结论,第一种比较无脑,如果感觉第一种比较抽象可以看第二种。

我们拿个式子来写一些:设以 \(i\) 为中心的矩形为 \(A_i\)\(i\) 能通过车站到达的矩形的集合的并为 \(B_i\),那么我们其实就是要求 \(\bigcap (A_i \cup B_i)\)

直接把这个东西拆开。由于我们发现如果 \(mid - dis_i \le mid - dis_j\),那么 \(B_i \cap B_j = B_j\),那么也就是说其实只有 \(mid - dis_j\) 最大的那个 \(B_i\) 是有贡献的。那么如果按照 \(mid - dis_j\) 从小到大排好序,那么上面那个式子其实等于 \((B_1) \cup (A_1 \cap B_2) \cup (A_1 \cap A_2 \cap B_3) \cup \cdots\)

换一种方式理解,如果某个人决定要到车站,那么如果有的人可以到车站,且他到车站比前面那个人距离还短,那么这个人直接跟着那个人走就可以了,所以只需要考虑到车站且距离最大的那个人即可。

那么问题就变成了要求上面那个式子是否为空集。由于 \(A_i\) 是一个矩形,可以 \(O(1)\) 计算交集,我们考虑维护 \(A_{1\sim i-1}\) 的交集,然后看它与 \(B_i\) 是否有交集即可。

那么我们可以用主席树维护出二维平面上某个矩形中是否有车站。如果 \(A\) 与某个 \(B\) 有交集,那么 \(B\) 的中心肯定在 \(A\) 往外扩大 \(B\) 的边心距的矩形内。这样我们就可以判断了。

求每个点距离最近的点也可以直接这么二分,方法很多就不说了。

CF765F Souvenirs (*3000)

很有意思的一道题。碰到区间询问可以考虑离线扫描线维护答案。

先只考虑 \(a_i < a_j, i < j\) 的贡献,大于的可以翻转过来再做一遍。

考虑加入一个数 \(a_i\) 会对右端点的答案造成什么贡献。我们先找到右面第一个大于等于 \(a_i\) 的数 \(a_j\),那么 \([j, n]\) 的答案就都要和 \(a_j - a_i\)\(\min\)。然后我们再找下一个大于等于 \(a_i\) 小于 \(a_j\) 的数 \(a_k\)\([k, n]\) 的答案与 \(a_k - a_i\)\(\min\),依此类推。这样的复杂度可以卡成 \(n^2\),但是发现如果 \(a_j - a_k < a_k - a_i\),那么这样的 \(k\) 是一定没有贡献的,于是我们只需要考虑 \(a_k < \frac{a+i + a_j}{2}\)\(k\),这样每次值域会减半,就只会计算 \(\log 10^9\) 次了。

找值域在某个区间内的点可以开一颗权值线段树维护位置的最小值,答案用一颗树状数组维护前缀最小值即可。

复杂度 \(O(n \log n \log 10^9)\)

CF1218B Guarding warehouses (*3000)

在笛卡尔坐标系上很简单,由于不交可以扫描线,用 set 维护当前这一列上的线段的先后顺序,面积就是两个矩形面积差。

放极坐标系上一模一样,改成极角排序然后面积算三角形面积差就行了。

计算几何狗都不写,这题口胡的。

CF176E Archaeology (*3100)

这不就 寻宝游戏 吗?

咋还 *3100 呢

CF896E Welcome home, Chtholly (*3100)

⌈突刺贯穿第二分块⌋

自闭了,分块太难了。

五彩斑斓的世界卡常卡不过去,弃了

upd. 卡过去了

首先对数列进行分块。然后我们考虑整块怎么修改:我们需要实现值域平移的操作,而注意到最大值是不增的,所以我们考虑类似势能分析的方式进行修改整块。

设最大值为 \(v\),那么分两种情况:

\(x \ge \frac{v}{2}\) 的时候,最大值会变成 \(x\),减少了 \(v - x\),我们可以枚举值域在 \([x + 1, v]\) 内的数,然后平移至 \([1, v - x]\),复杂度 \(O(v - x)\)

\(x < \frac{v}{2}\) 的时候,最大值会变成 \(v - x\),减少了 \(x\),我们可以枚举值域在 \([1, x]\) 内的数,然后平移至 \([x + 1, 2x]\),然后再维护一个全局减标记 \(delta\) 即可,复杂度 \(O(x)\)

这样我们的整块修改的复杂度就等于最大值减少的总量,即 \(O(v \sqrt{n})\)

散块就暴力重构。

具体维护使用并查集,就可以做到 \(O(\alpha(n))\) 合并两个值域内的所有点了。

CF679E Bear and Bad Powers of 42 (*3100)

发现 \(42\) 的幂并不多,那么也就是说对于操作三来说,重复执行的次数总数是有限的,所以我们来考虑势能线段树。

对于每一个点维护一下这个区间内有数可能变成 \(42\) 的幂需要加的最小值。这里的可能是指使这个数大于等于比它大的第一个 \(42\) 的幂。

区间赋值直接赋值,会增加 \(O(\log n)\) 段连续段。

区间加就直接加,如果当前加的数小于所需要的最小值,就直接打标记,否则递归下去,当碰到一个连续段的时候就直接对这个连续段进行修改,再判断一下是出现 \(42\) 的幂,如果有就一直加即可。

\(m\) 为每个数最多能变成多少 \(42\) 的幂,大约为 \(\log_{42} (10^9 \times q) \approx 10\)

分析复杂度:设势能函数 \(\Phi(x)\) 为线段树上每个点维护的最小值最多增加的次数之和,初始值为 \(O(n m)\)

对于一次区间赋值,会增加 \(O(\log n)\) 个连续段,\(\Phi(x)\) 增加 \(O(m \log n)\)

对于区间加,普通线段树节点访问 \(O(\log n)\) 个,额外递归线段树节点的最小值一定会增加,\(\Phi(x)\) 减少 \(O(1)\),每次操作最多会增加两个连续段,\(\Phi(x)\) 增加 \(O(2 m \log n)\)

这样 \(\Phi(x)\) 的总增加量为 \(O((n + q) m \log n)\),即总复杂度为 \(O((n + q) m \log n)\)

说明不合法的数改成任意序列也能做。定义坏数为 1145141919810 的一个前缀

CF571D Campus (*3100)

很简单的题。

考虑怎么处理加和和设 \(0\)。我们可以不真的设 \(0\),而是拿一颗线段树记录每个数最后一次被设为 \(0\) 的时刻,然后拿主席树维护那个时刻的值,拿现在的减去那时候的值就是答案了。

两类集合可以先离线下来然后重标号,将连通块变成一段连续的值域,然后就可以线段树修改了。

CF407E k-d-sequence (*3100)

特判掉 \(d=0\) 的情况。

显然\(\bmod d\) 不相等的数肯定不能在同一个区间内。

相等的可以把他们都除以 \(d\),这样变成看 \([l, r]\) 内是否覆盖连续值域,少最多 \(k\) 个。

经典 \((\max - \min) - (r - l)\),这次是找满足 \((\max - \min) - (r - l) \le k\) 的最小值,二分一下即可。

注意不能有重复的数,所以记录一下每个数上次出现的位置,左边界每次取 \(lst _{a_r}+ 1\) 即可。

CF700D Huffman Coding on Segment (*3100)

难点在于知道 Huffman 编码咋做(

简单来说:建一颗二叉树,每个叶子表示一个字符,把它看做一个自动机,就能解码了。长度就等于每个叶子节点表示的数的出现次数 \(f_i\) 乘它的深度 \(d_i\),即 \(\sum f_id_i\)

可以看做 \(n\) 个点的森林,每次合并两个树,答案会增加两个森林中 \(\sum f_i\) 的和,其实就是合并果子,贪心即可。

然后考虑这题。首先我们要求区间中每个数出现的次数 \(f_i\),上个莫队。

然后观察到 \(\sum f_i = O(n)\),所以考虑根号分治。对于 \(f_i \le B\) 的,可以从小到达扫,自己和自己匹配,剩下的往下匹配,可以 \(O(B)\) 计算出,对于 \(f_i > B\) 的,直接用优先队列做,复杂度 \(O(\frac{n}{B} \log n\),取 \(B = \sqrt{n \log n}\),则询问复杂度为 \(\sqrt{n \log n}\),总复杂度就是 \(O(n \sqrt{q} + q\sqrt{n \log n})\)

CF633H Fibonacci-ish II (*3100)

怎么全是一些基础套路题

区间排序去重一眼莫队 然后线段树维护一下即可

线段树维护斐波那契数经典做法就是维护斐波那契数的转移矩阵,让 \(f_i \gets f_{i + 1}\) 只需给线段树区间乘一个矩阵即可。

然后随便维护下,复杂度 \(O(n \sqrt{q} \log n)\)

and 写莫队的时候请对数列分块,而不是询问分块。

for (int l = 1, r, i = 1; l <= q; l = r + 1, i++) {
	r = min(q, l + B - 1);
	for (int j = l; j <= r; j++) {
		bl[j] = i;
	}
}

CF453E Little Pony and Lord Tirek (*3100)

考虑每个值有两种情况:

  1. \(t \le \frac{m_i}{r_i}\):此时的 \(s_i = t \cdot r_i\)
  2. \(t > \frac{m_i}{r_i}\):此时的 \(s_i = m_i\)

假如 \(t\) 全都相等,那么我们可以用一个主席树来求出 \(\sum r_i\)\(\sum m_i\)

但是进行区间修改之后, \(t\) 不完全相等了。但是发现连续颜色段数量是 \(O(n)\) 的,而每次访问之后会把所有访问过的颜色段全部删除,所以我们上珂朵莉树来维护这个东西复杂度是对的。

对于有初值的情况,特判一下即可。

没写过珂朵莉树,现学了一下。

CF536E Tavas on the Path (*3100)

很简单的题吧。

考虑如果 01 固定了,我们只需要树剖一下维护这个答案。中间部分可以直接记录出答案,前缀后缀可以记录一下长度,合并很容易合并。

那么 01 没固定,我们考虑把询问离线下来,然后按照 \(l\) 从大往小询问,这样我们就是一个不断将 \(0\) 设置为 \(1\) 的过程,直接线段树维护即可。

CF855F Nagini (*3100)

Segment Tree Beats 板子。

考虑如何维护那个答案,发现我们都是取 \(\min\) 操作,我们可以用 set 维护所有没有被覆盖过的点,用另外两颗线段树维护答案。当这个节点被正负都覆盖过后,将其加入第二棵线段树。第二棵线段树初值设为 \(0\),这样直接区间取 \(\min\) 不会有影响。

CF1332G No Monotone Triples (*3100)

很神奇的题。

首先看答案没给上界,就能猜到答案应该是常数级别的。

再模拟一下,发现长度最长为 \(4\)

那么我们就只需要判断是否有 \(3, 4\) 的子序列,输出一个方案即可。

类似于支配对的想法,我们求出对于每一个左端点,其最近的合法的右端点在哪里,这样,我们只需要找到区间内右端点的最小值,如果在区间内那么这一对就合法。

长度为 \(3\)

发现只要区间不是全部单调,就一定存在长度为 \(3\) 的区间。假如区间不完全单调,那么其间必定存在这样一个转折点满足左边的数与右面的数都比它大/小。

那么我们可以对于每一个点 \(i\),找出其左边第一个不等于的数 \(pre_i\) 与右边第一个不等于的数 \(nxt_i\),如果 \(pre_i, nxt_i\)\(i\) 的大小关系相同,那么我们就可以记录 \((pre_i, i, nxt_i)\) 这一对可能答案。

长度为 \(4\)

手模一下,发现长度为 \(4\) 合法的充要条件是:\(b_1, b_4\) 不是这四个数中的最大值或最小值。

那么其实就是说,我们要找出的这一对 \((b_1, b_4)\) 要满足 \(b_1\) 后面与 \(b_4\) 前面有严格比它大和严格比它小的点。

考虑建两个单调栈,一个单调不增一个单调不减。那么我们发现,对于某一个 \(b_1\),我们通过二分栈找出其后第一个严格大与严格小的位置,并找出这两个位置后面第一个不在栈中的元素,那么这个元素就是最小的 \(b_4\)。因为一个元素不在栈中说明它前面有严格大于它和严格小于它的元素。

这时候我们再记录一下严格大/小的前驱后继。我们就可以找出中间的两个点是什么了。

submission

CF1476G Minimum Difference (*3100)

基础莫队练习题。

区间颜色数直接上带修莫队。我们需要一个 \(O(1)\) 插入删除修改的数据结构,直接上个链表来维护。

查询的时候可以直接把所有出现次数拿出来排序,跑双指针。容易证明出现次数的种类是 \(O(\sqrt n)\) 的。(根号分治,小于等于 \(\sqrt n\) 的显然,大于 \(\sqrt n\) 的只有 \(\sqrt n\) 个,所以总种类数是 O(\sqrt n) 的。)

那么总复杂度就是 \(O(n^{\frac{5}{3}} + n \sqrt n \log n)\)

需要手写链表,std::list 跑的太慢了。

CF418E Tricky Password (*3100)

首先手模一下发现除了第一行之外,奇数行都相等,偶数行都相等。证明也很容易,因为这个过程实际上是将相同的数看作一条链,给链标号,然后第二次操作就是把标号又改回了链的标号。

那么我们现在就只需要维护三行。考虑修改第一行,维护出第二行,然后第三行直接查询第二行的出现次数。

感觉出现次数就不太能 polylog,考虑分块。对于每一个块,维护出每种颜色在区间内的出现次数区间、出现次数的出现次数、每个点在块中相同的颜色的排名。根据排名和出现次数区间可以计算出这个点的实际值。

当我们单点修改时,对后面的整块来说,出现次数的区间会向左或向右移动一位,而此时出现次数的出现次数只修改了两个,所以容易维护出现次数的出现次数。

于是第三行也就容易查询了。

CF960H Santa's Gift (*3100)

比较水。

题目其实求的就是 \(\sum_{i=1}^n (c_k cnt_{i, k} + C)^2\),容易拆开,维护的实际就是 \(\sum_{i=1}^n cnt_{i, k}\)\(\sum_{i=1}^n cnt_{i, k}^2\)。修改操作实际上就是链加,直接树剖线段树维护即可。

线段树维护平方和:考虑 \((x+v)^2 = x^2 + 2vx + v^2\),容易维护。

需要动态开点。

CF643G Choosing Ads (*3200)

很有趣的一道题。

首先令 \(p \gets \lfloor\frac{p}{100}\rfloor\),那么我们可以把问题转化成求出所有出现次数 \(\ge \frac{n}{p + 1}\) 的至多 \(p\) 个数。

考虑 \(p=1\) 的时候,发现这个问题就是一个主元素的问题,而区间主元素有经典的摩尔投票合并法。考虑将这个做法进行拓展。

考虑摩尔投票法的本质,实际上是每次将两个不相等的数配对删去,然后最后仅会剩下一个数。那么考虑直接拓展为每次将 \(p+1\) 个不相等的数配对删去,然后剩下至多 \(p\) 个数。

正确性:这样至多配对 \(\lfloor\frac{n}{p + 1}\rfloor\) 次,那么对于所有出现次数大于 \(\frac{n}{p}\) 的数,最坏情况下也会有剩余(\(\lfloor\frac{n}{p + 1}\rfloor \le \frac{n}{p}\)),所以这样一定能够留下所有出现次数大于 \(\frac{n}{p}\) 的数。那么直接线段树上维护这个过程就做完了。区间修改很容易实现。

CF1209G2 Into Blocks (hard version) (*3200)

妙妙题?

首先考虑不带修怎么做。对于两个颜色,如果它们的出现区间有重叠,那么这两个颜色最终一定会被染成同一个颜色,而这样的关系就会将颜色划分成若干个连通块。这样,我们只需要将每个连通块中的颜色全部改为出现次数最多的那个颜色即可。实际上,由于区间重叠的关系,连通块一定对应着原序列上的一段连续区间。

那么我们的问题就变成了:将序列划分成若干个区间,使得每段区间中包含的所有颜色不在区间外出现,且最大化每段区间内的颜色出现次数最大值之和。

我们考虑将划分的区间变为左闭右开区间。我们将每种颜色的出现区间(左闭右开)整体加 1,这样我们发现,划分的位置一定是等于 \(0\) 的位置。那么我们的问题就转化成了:维护一个序列,支持区间加减,求所有 \(0\) 划分开的序列中每一段序列中的出现次数最大值。我们可以直接把权值放在第一个出现位置,这样就是区间最大值了。

直接维护 \(0\) 的划分并不是很好维护,但是由于每个位置的数一定 \(\ge 0\),所以我们可以改成维护由最小值划分的区间,这样就能够在线段树上方便的维护了。具体来讲就是维护一个前缀后缀与全局最大值,还有这个区间内的答案。合并时只需要看左右区间是否最小值相等,如果相等就将左区间后缀最大值与右区间前缀最大值取 \(\max\) 累计到答案上,并加起来左右区间的答案,否则就只统计最小值一边的答案。单点修改只需要对每一个颜色维护一个 set 即可。

CF1270H Number of Components (*3300)

Into Blocks (hard version) 类似的套路。

首先观察发现一件事情,就是同一个连通块一定是连续的一段区间,且对应着连续的一段值域。

那么我们就是要求,将区间划分成最多的子段,使得子段之间的值域两两不交且递减。

这个我们可以用同样的思路,改成维护左闭右开区间上的覆盖次数最小值,这样最小值的个数就是答案。具体来讲就是将每两个相邻的数 \(x, y\) 对应到 \(\lbrack\min(x, y), max(x, y))\) 的一段区间上,这样每个 \(0\) 个位置就是一个合法的划分点。统计最小值个数即可。

CF1137F Matches Are Not a Child's Play (*3400)

思路清晰,代码简短,LCT 好题!

首先观察进行一次 up 操作之后删除序列发生的变化。考虑原来的最大值 \(y\) 到操作的节点 \(x\) 的路径,在原序列上,我们发现,\(x-y\) 这条路径的相对删除顺序一定是 \(x \to y\)(因为 \(y\) 此时为最大值,\(x\) 肯定比 \(y\) 晚删除),而当 \(x\) 变为新的最大值之后,删除顺序变为了 \(y \to x\),而又因为 \(x, y\) 此时为最大与次大值,那么这条路径上的所有点都变成了最后删除。同时不难发现,这条路径以外的所有点的删除顺序不变。

那么我们实际上要支持的操作就是:

  1. 将一条路径上的点的相对顺序翻转,并将其放到序列末尾;
  2. 查询一个点在序列中的位置。

第一个操作看起来还是很奇怪,我们可以给每一个点再染一个颜色,定义两个点的先后顺序为以颜色为第一关键字,优先级为第二关键字的二元组比较,这样我们的操作就改变成了给一条路径上的点的优先级翻转并染色。

先考虑染色怎么做。这个操作类似于 P3703 [SDOI2017]树点涂色。我们考虑 LCT 的 Access 的过程,这个过程其实就是将若干条实链断开,并将若干条链合并成一条新的实链。那么我们就可以在 Access 的同时,将断开的实链的颜色进行更新,并将这若干条链染色。那么也不难发现,翻转也是很简单的。实际上,这个操作就是 LCT 上的 makeRoot 操作。

具体计算答案时,一个点的排名就是颜色比它小的点的总数加上颜色相同,深度比它大的点数。前者可以使用树状数组维护每个颜色的点数,在 Access 的断链与合并链的时候直接动态维护一下,后者由于同一种颜色一定对应着 LCT 上的一颗 Splay,所以直接在相对应的 Splay 上查询即可。

CF1034D Intervals of Intervals (*3500)

一点也不难,但是一步也想不到.. qwq

一开始的想法:首先二分答案,然后拿线段树随便维护一下并的大小,双指针扫一遍就完了,复杂度 \(O(n \log^2 n)\)。然后突然意识到,这样只能找到第 \(k\) 大是几,但是根本没法求和。可以二分找到每个答案的区间?那样复杂度加个 \(n\),无法接受。然后就不会了。

其实正解非常简单,我们尝试线段树扫描线维护出每个左端点的答案,这样求和就能做了。具体来讲,我们可以维护每个点最后一次被覆盖的时间,这样当这个点被第二次覆盖时,其实就是对所有在 \([t' + 1, t]\) 这段区间的左端点答案加 \(1\)。线段树显然维护不了这东西,但是我们发现区间赋值可以颜色段均摊,所以直接拿珂朵莉树维护即可,每次把所有区间全部删掉并且对应的在线段树上区间加,这样我们就可以做到单次 \(O(n \log n)\) 了,加上外层二分就是 \(O(n \log n \log V)\)

实际上可以继续优化,首先珂朵莉树的过程只需要进行一次,然后将所有修改先记录下来,这样 set 的一个 \(\log\) 就消掉了。扫描线用线段树维护其实是没有必要的,还是沿用最一开始的想法,直接双指针扫就行了,区间加和直接用差分维护,这样复杂度就是 \(O(n \log n)\) 了,而且比线段树好写。

posted @ 2022-12-20 20:32  APJifengc  阅读(1156)  评论(3编辑  收藏  举报