Codeforces Data Structures *3000 乱写
开个坑。大致会持续更新。
所有题搬自 nzhtl1477 的课件。
好多都是老题,可能真正难度到不了 *3000。
或许可以说是套路数据结构大全。
斜体标注的题目是相对水一点 / 没什么意义的题,大多分布在前面(大概)。
- CF526F Pudding Monsters (*3000)
- CF464E The Classic Problem (*3000)
- CF603E Pastoral Oddities (*3000)
- CF1446D2 Frequency Problem (Hard Version) (*3000)
- CF150E Freezing with Style (*3000)
- CF997E Good Subsegments (*3000)
- CF319E Ping-Pong (*3000)
- CF696E ...Wait for it... (*3000)
- CF1163F Indecisive Taxi Fee (*3000)
- CF436F Banners (*3000)
- CF793F Julia the snail (*3000)
- CF1178G The Awesomest Vertex (*3000)
- CF773E Blog Post Rating (*3000)
- CF331D3 Escaping on Beaveractor (*3000)
- CF185E Soap Time! - 2 (*3000)
- CF765F Souvenirs (*3000)
- CF1218B Guarding warehouses (*3000)
- CF176E Archaeology (*3100)
- CF896E Welcome home, Chtholly (*3100)
- CF679E Bear and Bad Powers of 42 (*3100)
- CF571D Campus (*3100)
- CF407E k-d-sequence (*3100)
- CF700D Huffman Coding on Segment (*3100)
- CF633H Fibonacci-ish II (*3100)
- CF453E Little Pony and Lord Tirek (*3100)
- CF536E Tavas on the Path (*3100)
- CF855F Nagini (*3100)
- CF1332G No Monotone Triples (*3100)
- CF1476G Minimum Difference (*3100)
- CF418E Tricky Password (*3100)
- CF960H Santa's Gift (*3100)
- CF643G Choosing Ads (*3200)
- CF1209G2 Into Blocks (hard version) (*3200)
- CF1270H Number of Components (*3300)
- CF1137F Matches Are Not a Child's Play (*3400)
- CF1034D Intervals of Intervals (*3500)
CF526F Pudding Monsters (*3000)
这题居然已经降到蓝了..
upd. 又评回紫了,不知道什么情况。
经典套路了,对于每一个右端点,维护
CF464E The Classic Problem (*3000)
很无脑的一题。
乍一看最短路板子,但是值域是
但是完全可以直接套 Dijkstra。我们考虑 Dijkstra 需要哪些操作:某个数+边权,比较大小。
因为边权都是
然后是比较,可以维护一个哈希值,然后在线段树上二分 LCP 就行了。
由于对于每一个点都需要记录,线段树可持久化一下就行了。
CF603E Pastoral Oddities (*3000)
每个点的度数都是奇数这个限制很奇怪,我们找一个等价条件。
首先不难想到如果度数都是奇数,那么连通块大小不可能为奇数。
由此可以发现,如果一个连通块的点数为偶数,那么一定可以从这个连通块中找出一个边集,使得度数都为奇数。
证明考虑找出一颗生成树,然后从叶子开始往上删,如果这个点的度数为偶数就删除它与父亲的连边,这样如果除了根节点的度数都是奇数,根节点的度数一定也是奇数。
生成树与最大值最小可以启发我们去考虑维护一颗最小生成森林。维护直接使用 LCT 即可。
加边就是正常的加,然后需要维护一下每个连通块的大小,这个需要维护 LCT 上的子树信息,只需要在 LCT 上维护虚子树的和即可。
然后每加完一条边,我们尝试从大往小去删边,一直删到出现连通块大小为奇数的结束。删掉最后这一条边之后没有了合法方案,说明这条边一定存在于选择边集的方案里,而我们是从大到小删的边,说明这条边就是边集里最大的那条边,于是这条边的边权便是答案。
太久没写过 LCT 了,这东西调了一下午,呃呃。
CF1446D2 Frequency Problem (Hard Version) (*3000)
首先有一个结论:原序列的众数一定是答案区间的众数。
考虑从原区间不断删数,这样删到某一时刻会出现一个数与原众数的出现次数相等,因此原众数一定是答案区间的众数。
设原区间的众数为
考虑再进行一步题意转化:我们改成求最长的一个区间,满足
为什么这个与原问题等价呢?同样的道理,如果
那么有一种朴素的想法:枚举
那么我们可以将
这个复杂度是
对于出现次数大于
对于出现次数小于等于
CF150E Freezing with Style (*3000)
考虑二分中位数
这样假如某个路径的中位数大于等于
那么我们就相当于要找出是否存在一条路径,使得它的长度在
考虑点分治,由于我们只需要考虑是否存在,那么我们只需要存权值和的最大值。
那么我们对于每个长度,拿线段树维护这个长度的权值和的最大值,然后每插入一条路径的时候看权值和是否大于等于
CF997E Good Subsegments (*3000)
这题和 Pudding Monster 一模一样,只不过加了一个区间查询。
直接把询问挂在右端点上,每次查询改成区间查询即可。
CF319E Ping-Pong (*3000)
考虑两个区间之间会如何连边。
如果两个区间相交,那么它们连双向边。
如果两个区间是包含关系,那么会从小区间往大区间连。
并且发现区间长度递增,那么新加的区间只可能是与之前的区间相交或包含某些区间,不可能被某个区间包含。
那么我们可以用并查集将连双向边的点缩起来。此时需要维护现在区间的左端点与右端点跨过了哪些区间,直接扔线段树上维护然后暴力合并即可。
查询的时候,如果两个区间属于同一个连通块那肯定可以,否则可能是连单向边,也就是第一个区间包含于第二个区间,那么维护一下每个连通块的左右端点即可。
CF696E ...Wait for it... (*3000)
这题为啥能评到 3000...?
每次找一条路径删除前
子树加就打个标记就完了。可以标记永久化一下,这样比较好写。
CF1163F Indecisive Taxi Fee (*3000)
有趣题。
首先分情况讨论:我们先跑出一条
如果修改的边比原来权值小,且在最短路上,那么答案肯定是这条最短路减去少了的权值。
如果修改的边比原来权值小,但不在最短路上,那么最后最短路要不然就是原最短路,要不然就是
如果修改的边比原来权值大,且不在最短路上,那答案就是原最短路。
重点看最后一种情况。首先最后答案要不然就是原最短路,要不然就是一条不经过这条边的最短路。因为如果不是原最短路且经过了这条边,那么选原最短路一定更优(不劣)
我们钦定这条路径经过某条不在原最短路上的路径
那么我们预处理出前缀与后缀,在线段树上区间取
CF436F Banners (*3000)
分块凸包,套路题。
CF793F Julia the snail (*3000)
考虑对每一个右端点维护左端点答案。
考虑某一条绳子
CF1178G The Awesomest Vertex (*3000)
分块凸包,套路题。
CF773E Blog Post Rating (*3000)
首先考虑最优答案,考虑交换相邻两个逆序对,发现答案一定不劣,所以最优的答案肯定是
然后考虑这东西长什么样,发现一定是先有一段不断降低,然后当
我们首先找到这个
然后是后面的单调不降部分,可以写出来式子,发现答案就是
CF331D3 Escaping on Beaveractor (*3000)
题其实很简单,考虑按照经过的线段数进行倍增,然后记录经过这么多线段需要花多少时间,然后倍增处理即可。
需要处理出来沿着每个线段一直走能够到达哪个线段或到达边界,这个可以跑四遍扫面线实现。
想想都难写,看题解两篇都写了 8KB 代码,我觉得我还是算了吧。
CF185E Soap Time! - 2 (*3000)
考虑我们现在找出了一个点,那么答案就是所有点到这个点的时间的最大值,我们要让这个最大值最小,不难想到二分答案。
那么我们首先二分一个答案
考虑一个点能够到达的点集是一个菱形。这个菱形很丑,所以我们先考虑将曼哈顿距离转成切比雪夫距离,这样能到达的点集就是一个矩形了。
然后我们现在要考虑的就是这几个集合是否有交。首先每个点可以走以自己为中心的
下面有两种方式能够得出一个结论,第一种比较无脑,如果感觉第一种比较抽象可以看第二种。
我们拿个式子来写一些:设以
直接把这个东西拆开。由于我们发现如果
换一种方式理解,如果某个人决定要到车站,那么如果有的人可以到车站,且他到车站比前面那个人距离还短,那么这个人直接跟着那个人走就可以了,所以只需要考虑到车站且距离最大的那个人即可。
那么问题就变成了要求上面那个式子是否为空集。由于
那么我们可以用主席树维护出二维平面上某个矩形中是否有车站。如果
求每个点距离最近的点也可以直接这么二分,方法很多就不说了。
CF765F Souvenirs (*3000)
很有意思的一道题。碰到区间询问可以考虑离线扫描线维护答案。
先只考虑
考虑加入一个数
找值域在某个区间内的点可以开一颗权值线段树维护位置的最小值,答案用一颗树状数组维护前缀最小值即可。
复杂度
CF1218B Guarding warehouses (*3000)
在笛卡尔坐标系上很简单,由于不交可以扫描线,用 set 维护当前这一列上的线段的先后顺序,面积就是两个矩形面积差。
放极坐标系上一模一样,改成极角排序然后面积算三角形面积差就行了。
计算几何狗都不写,这题口胡的。
CF176E Archaeology (*3100)
这不就 寻宝游戏 吗?
咋还 *3100 呢
CF896E Welcome home, Chtholly (*3100)
⌈突刺贯穿第二分块⌋
自闭了,分块太难了。
五彩斑斓的世界卡常卡不过去,弃了
upd. 卡过去了
首先对数列进行分块。然后我们考虑整块怎么修改:我们需要实现值域平移的操作,而注意到最大值是不增的,所以我们考虑类似势能分析的方式进行修改整块。
设最大值为
当
当
这样我们的整块修改的复杂度就等于最大值减少的总量,即
散块就暴力重构。
具体维护使用并查集,就可以做到
CF679E Bear and Bad Powers of 42 (*3100)
发现
对于每一个点维护一下这个区间内有数可能变成
区间赋值直接赋值,会增加
区间加就直接加,如果当前加的数小于所需要的最小值,就直接打标记,否则递归下去,当碰到一个连续段的时候就直接对这个连续段进行修改,再判断一下是出现
设
分析复杂度:设势能函数
对于一次区间赋值,会增加
对于区间加,普通线段树节点访问
这样
说明不合法的数改成任意序列也能做。定义坏数为 1145141919810 的一个前缀
CF571D Campus (*3100)
很简单的题。
考虑怎么处理加和和设
两类集合可以先离线下来然后重标号,将连通块变成一段连续的值域,然后就可以线段树修改了。
CF407E k-d-sequence (*3100)
特判掉
显然
相等的可以把他们都除以
经典
注意不能有重复的数,所以记录一下每个数上次出现的位置,左边界每次取
CF700D Huffman Coding on Segment (*3100)
难点在于知道 Huffman 编码咋做(
简单来说:建一颗二叉树,每个叶子表示一个字符,把它看做一个自动机,就能解码了。长度就等于每个叶子节点表示的数的出现次数
乘它的深度 ,即 。 可以看做
个点的森林,每次合并两个树,答案会增加两个森林中 的和,其实就是合并果子,贪心即可。
然后考虑这题。首先我们要求区间中每个数出现的次数
然后观察到
CF633H Fibonacci-ish II (*3100)
怎么全是一些基础套路题
区间排序去重一眼莫队 然后线段树维护一下即可
线段树维护斐波那契数经典做法就是维护斐波那契数的转移矩阵,让
然后随便维护下,复杂度
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)
考虑每个值有两种情况:
:此时的 ; :此时的 。
假如
但是进行区间修改之后,
对于有初值的情况,特判一下即可。
没写过珂朵莉树,现学了一下。
CF536E Tavas on the Path (*3100)
很简单的题吧。
考虑如果 01 固定了,我们只需要树剖一下维护这个答案。中间部分可以直接记录出答案,前缀后缀可以记录一下长度,合并很容易合并。
那么 01 没固定,我们考虑把询问离线下来,然后按照
CF855F Nagini (*3100)
Segment Tree Beats 板子。
考虑如何维护那个答案,发现我们都是取
CF1332G No Monotone Triples (*3100)
很神奇的题。
首先看答案没给上界,就能猜到答案应该是常数级别的。
再模拟一下,发现长度最长为
那么我们就只需要判断是否有
类似于支配对的想法,我们求出对于每一个左端点,其最近的合法的右端点在哪里,这样,我们只需要找到区间内右端点的最小值,如果在区间内那么这一对就合法。
长度为
发现只要区间不是全部单调,就一定存在长度为
那么我们可以对于每一个点
长度为
手模一下,发现长度为
那么其实就是说,我们要找出的这一对
考虑建两个单调栈,一个单调不增一个单调不减。那么我们发现,对于某一个
这时候我们再记录一下严格大/小的前驱后继。我们就可以找出中间的两个点是什么了。
CF1476G Minimum Difference (*3100)
基础莫队练习题。
区间颜色数直接上带修莫队。我们需要一个
查询的时候可以直接把所有出现次数拿出来排序,跑双指针。容易证明出现次数的种类是
那么总复杂度就是
需要手写链表,std::list
跑的太慢了。
CF418E Tricky Password (*3100)
首先手模一下发现除了第一行之外,奇数行都相等,偶数行都相等。证明也很容易,因为这个过程实际上是将相同的数看作一条链,给链标号,然后第二次操作就是把标号又改回了链的标号。
那么我们现在就只需要维护三行。考虑修改第一行,维护出第二行,然后第三行直接查询第二行的出现次数。
感觉出现次数就不太能 polylog,考虑分块。对于每一个块,维护出每种颜色在区间内的出现次数区间、出现次数的出现次数、每个点在块中相同的颜色的排名。根据排名和出现次数区间可以计算出这个点的实际值。
当我们单点修改时,对后面的整块来说,出现次数的区间会向左或向右移动一位,而此时出现次数的出现次数只修改了两个,所以容易维护出现次数的出现次数。
于是第三行也就容易查询了。
CF960H Santa's Gift (*3100)
比较水。
题目其实求的就是
线段树维护平方和:考虑
需要动态开点。
CF643G Choosing Ads (*3200)
很有趣的一道题。
首先令
考虑
考虑摩尔投票法的本质,实际上是每次将两个不相等的数配对删去,然后最后仅会剩下一个数。那么考虑直接拓展为每次将
正确性:这样至多配对
CF1209G2 Into Blocks (hard version) (*3200)
妙妙题?
首先考虑不带修怎么做。对于两个颜色,如果它们的出现区间有重叠,那么这两个颜色最终一定会被染成同一个颜色,而这样的关系就会将颜色划分成若干个连通块。这样,我们只需要将每个连通块中的颜色全部改为出现次数最多的那个颜色即可。实际上,由于区间重叠的关系,连通块一定对应着原序列上的一段连续区间。
那么我们的问题就变成了:将序列划分成若干个区间,使得每段区间中包含的所有颜色不在区间外出现,且最大化每段区间内的颜色出现次数最大值之和。
我们考虑将划分的区间变为左闭右开区间。我们将每种颜色的出现区间(左闭右开)整体加 1,这样我们发现,划分的位置一定是等于
直接维护 set
即可。
CF1270H Number of Components (*3300)
和 Into Blocks (hard version) 类似的套路。
首先观察发现一件事情,就是同一个连通块一定是连续的一段区间,且对应着连续的一段值域。
那么我们就是要求,将区间划分成最多的子段,使得子段之间的值域两两不交且递减。
这个我们可以用同样的思路,改成维护左闭右开区间上的覆盖次数最小值,这样最小值的个数就是答案。具体来讲就是将每两个相邻的数
CF1137F Matches Are Not a Child's Play (*3400)
思路清晰,代码简短,LCT 好题!
首先观察进行一次 up
操作之后删除序列发生的变化。考虑原来的最大值
那么我们实际上要支持的操作就是:
- 将一条路径上的点的相对顺序翻转,并将其放到序列末尾;
- 查询一个点在序列中的位置。
第一个操作看起来还是很奇怪,我们可以给每一个点再染一个颜色,定义两个点的先后顺序为以颜色为第一关键字,优先级为第二关键字的二元组比较,这样我们的操作就改变成了给一条路径上的点的优先级翻转并染色。
先考虑染色怎么做。这个操作类似于 P3703 [SDOI2017]树点涂色。我们考虑 LCT 的 Access 的过程,这个过程其实就是将若干条实链断开,并将若干条链合并成一条新的实链。那么我们就可以在 Access 的同时,将断开的实链的颜色进行更新,并将这若干条链染色。那么也不难发现,翻转也是很简单的。实际上,这个操作就是 LCT 上的 makeRoot
操作。
具体计算答案时,一个点的排名就是颜色比它小的点的总数加上颜色相同,深度比它大的点数。前者可以使用树状数组维护每个颜色的点数,在 Access 的断链与合并链的时候直接动态维护一下,后者由于同一种颜色一定对应着 LCT 上的一颗 Splay,所以直接在相对应的 Splay 上查询即可。
CF1034D Intervals of Intervals (*3500)
一点也不难,但是一步也想不到.. qwq
一开始的想法:首先二分答案,然后拿线段树随便维护一下并的大小,双指针扫一遍就完了,复杂度
其实正解非常简单,我们尝试线段树扫描线维护出每个左端点的答案,这样求和就能做了。具体来讲,我们可以维护每个点最后一次被覆盖的时间,这样当这个点被第二次覆盖时,其实就是对所有在
实际上可以继续优化,首先珂朵莉树的过程只需要进行一次,然后将所有修改先记录下来,这样 set
的一个
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】