[Done] 省选数据结构题目的做

这个系列用于记录学习省选知识点的过程中做题的笔记,系列名就是这样

因为省选的知识点真的是又多又杂,题单也是又难又长,不排除同时多个题单一起开工的情况,所以如果这一部分完成了就是 [done] 的前缀,做中就是 [working]

可能会跳过一些 lxl 题

2024.12.15 基本完成,剩下一些零散知识点慢慢更,大头的还有树套树没动,但是学了 CDQ

LG P2023

线段树 2 板子题,一眼秒。取模比较沙币,直接上 __int128 比较舒适。

LG P4513

这是一个经典套路题了,线段树维护区间最大子段和,对于每个 node 维护:

  • lmax:从左端点向右延伸的最大子段和
  • rmax:从右端点向右延伸的最大子段和
  • sum:区间和

注意一下 seg.query() 的区间。

LG P1471

跟着 @DPair 推一下公示,转化一下 Pushdown

LG P6327

维护少见的区间信息有常见的两个套路化思考方法:

  1. 拆分成维护好的信息
  2. 直接维护,考虑修改对其的影响

这里是和角公式:

sin(x+v)=sinxcosv+cosxsinvcos(x+v)=cosxcosvsinxsinv

运用法 2,我们使用和角公式来通过维护区间 sin,cos 和来 O(1) 地下传标记。

LG P3792

数学题都被 Ynoi 除名了

线段树维护区间 min,max 区间和区间平方和。

通过 max,min 算出如果这是个连续段,那么其平方和应该是多少。

因为是 lxl 题所以同时维护区间最大最小值,区间和,区间平方和和区间立方和。

给两组公式:

i=1ni2=n(n+1)(2n+1)6i=1ni3=(n(n+1)2)2

LG P5278

码起来很恶心。

考虑组成等差数列的要求:

  • seqmaxseqmin=d(rl)
  • i,j,k[1,n],ijkgcd(|aiaj|,|ajak|)=d
  • i,j[1,n]aiaj

使用线段树 + map + set 维护最大值,最小值,前驱最大值,区间左端点值,区间右端点值,差的 gcd

都说数据水我还是写了一天,服了。

LG P6617

定义一个数的前驱为这个位置之前第一个与这个位置相加和为 w 的位置,没有就是 0,答案为检查区间内所有数的前驱是否 l

转化!如果这个数的前驱比它前面第一个与它相同的位置要小,直接将其设为 0 是正确的。

然后这样修改之后新变化的位置最多有 5 个:现位置、现位置原后面第一个与其相同的位置、现位置原后面第一个相加得 w 的位置、现位置修改后第一个与其相同的位置,现位置修改后第一个与其相加得 w 的位置

又恨又爱的 set 维护一下位置,上线段树修改。

LG P5069

注意到操作位置 x 时,必有 ax>ax1&axax1。所以找到满足的 xx1,x+1 显然都不会被操作,只能一直把 x 减到 0,所以答案就是 ax

把序列抽离出来变成连续的不降序列和上升序列交替出现。对于每一小段不降序列或者上升序列,其最大值必定会被操作到。最大值在端点外时,会带走相邻的一个,两个两个地带走最大值,操作位置的 奇偶性 不变,对于奇数和偶数位置分别维护一颗 Fenwick 记录和。对于每小段子序列记录下极值点,用 std::set 维护。

修改时只考虑修改极值点,首先把 x1,x,x+1 从位置中删除,加入新极值点时,新的极值点只可能在修改的位置的 x1,x,x+1 中出现。

良心 Ynoi 不卡常,O(nlogn) 过。

LG P4139

其实是下一道题的前置知识要求。

对于任意的 bφ(p),我们有:

ababmodφ(p)+φ(p)(modp)

对于任意的 b<φ(p),则有:

ababmodφ(p)(modp)

这个题里显然套第一个公式递归往下做就可以了。

LG P3747

第一想法是:这尼玛能 SegTree 维护?

然后想到了扩展欧拉定理,然后可以发现这种脑瘫处理多半是有特殊性质的,参见 花神游历各国

这道题同理,我们套上扩展欧拉定理,暴力修改到 φ(φ(φ(φ(φ(φ(p))))))=1 之后不管了,因为此时已经变成了 ccccc,与 ai 无关,每个点最多会被修改 2logp 次,每次修改最多需要 log 次的递归,还有一只 log 的快速幂,用光速幂搞掉一只 log,整道题目就可以在 O(mlog2p) 的时间复杂度下解决。

一句话:扩欧+花神游历各国+光速幂。

以后看到这种特殊的维护信息不要慌,想一想相关的数学知识或者发掘一下特殊性质,特别是与极差有关或者类似的暴力修改。

LG P1486

pb_ds 拯救世界

显然是平衡树,但是我们平衡树并不能做到区间加减,所以考虑一个常见 Trick:维护全局工资下界,记录当前工资下界和原始值的差,每次查询减去这个差。

又有一个常用的平衡树 Trick:存放 std::pair<int, int> 而不是 int 用于防止重复元素(维护 metadata 太麻烦了吧)。

LG P4036

限制了询问次数,注意到离谱的 std::string 可过。

LG P5482

树状数组可做。对于这种题还是太过迟钝了,没有想到值域上建树的第一想法。

corner case 比较多要讨论起来小麻烦,但是按照 lxl 的 PPT 来看的话确实这道题的本质并不难:

  • ax+b>ccba
  • 值域上树状数组
  • 插入时取整,查询时前缀和

LG P3586

明显的贪心。

记录两棵 Fenwick,第一棵记录离散化后 1i 中数字的出现个数,第二棵树状数组记录离散化前 1i 数字出现的值的和。离线询问:

  • 对于操作 1:若之前的数是正数,第一棵 1,第二棵 ori;若之后的数是正数,第一棵 +1,第二棵 +oriori 表示原来的数)
  • 对于操作 2:将 >s 的个数求出来,再比较 c 和大于 s 的个数乘上 c 与剩余数字之和,若前者大则输出 NIE,否则输出 TAK

LG P3987

前面还有几道比较傻的平衡树,主要是调试恶心就懒得写了,从这个 lxl 题继续记录。

势能分析。什么平衡树?不懂啊,我们考虑用线段树来做这个问题。

显然操作 1 无法直接维护,那么我们考虑进行一个暴力。

维护 nstd::vector e,其中对于 ei 记录所有能被 i 整除的数的编号,对编号进行二分,每次遇到除法操作就在 ex 里找到符合条件的区间 [l,r] 然后暴力修改,我们考虑记录这个操作的次数到 t[] 里,如果操作的次数非常多,每操作一次就将不是 x 的倍数的数删掉,这样子暴力会快一点。

传统艺能,我们发现 5e5 大约 19,20 次左右去除以 2 就到 1 了,特判一下 1

跑得飞快。

有个强化卡常版 LG P5610,知识点学完之后会板刷 YNOI 的,不急。

LG P3224

平衡树启发式合并,当然也可以写线段树合并。

然后我们发现就这两个操作可以上 pb_ds,还要个并查集。然后就写完了。

pb_ds 大法好。

LG P3201

容易想到链表维护,暴力修改。但是暴力复杂度是错的,考虑启发式合并来维护信息。

LG P4197

类似永无乡的套路,离线下询问按照 x 升序排序,这样子我们就成功去掉了 hix 这一限制。

然后用并查集维护从 vi 开始能够经过的所有山峰,用权值线段树维护区间第 k 大,合并连通块时也要合并线段树。

不知道为什么好恶心啊这个题码起来,调了半天。

LG P5586 & P5350

恶心,太恶心了,写吐了。

其实除了区间复制之外就是普通的可持久化平衡树,但是由于这个空间问题我们需要进行一个定期重构。

平衡树什么的写起来最讨厌了。

LG P4097

李超树板子题,我对李超树的理解就是把线段看作区间,对于可能存在的更优情况左右递归下去标记永久化修改。

LG P3081

平衡树/ std::set 维护纵坐标可行性,扫描线维护横坐标可行性,需要离散化,复杂度是 O(nlogn) 的。

LG P3402

贯彻落实 pb_ds,这里我们使用裸的并查集 + __gnu_cxx::rope 来实现。

然后被卡常了,考虑对于每个点赋一个随机权值,比较权值来合并,用随机化把复杂度均摊下去,跑得飞快。

LG P2397

混进来一个画风清新的小可爱题。考虑摩尔投票法,看代码模拟一下可以发现,如果一个数作为这个序列的绝对众数,那么它其他所拥有的数字个数在和其他数字做一个类似对拼抵消的操作之后必定会有剩余,最后有剩余的数字必定就是区间绝对中暑了。

LG P3567

莫队,暴力数的复杂度是 O(nm) 的,能爆完。考虑区间内如果有满足条件的数,期望是在序列内猜两次就能猜到。考虑设计一个阈值,然后从区间内随机挑数字,如果满足就有答案,否则 在大多数情况下 是无解的。对于有答案但猜不到的概率是 12R1,其中 R 即为我们设置的随机次数上界,我取 40,加上常用的快读快写和莫队询问区间奇偶排序,足以通过此题。

LG P2839

其实从这个题来看就能理解一个很新颖的东西逐渐走向一个套路化 Trick 的历程了。

二分中位数是个常见的套路,对于不固定的区间,首先 [b+1,c1] 是必选的,为了最大化中位数,我们选取 [a,b]sufmax[c,d]premax。如果每个二分的值都开线段树显然是爆炸的,但是我们发现对于相邻的两个不同的数建出来的树就只有一处不同,考虑改成可持久化线段树维护。

LG P4899

抽象一下题意:从 s 出发经过编号 L 的点和从 e 出发经过编号 R 的点能够到达的所有点的交集是否为空。

要有用 Kruskal 重构树的套路化意识。先选取编号较小者生成最大生成树 tr1,再选取编号较小者生成最小生成树 tr2

根据 Kruskal 重构树的性质,我们在 tr1 上做树上倍增,找到 idL 的节点 xx 深度尽可能小;再在 tr2 上做树上倍增找到 idR 的节点 y,并且 y 的深度也尽可能小。

现在我们要判断在 tr1 中以 x 为根的子树和在 tr2 中以 y 为根的子树的交集是否为空,考虑 dfs 维护出 dfn,然后上棵主席树维护一下。

LG P3302

树上第 k 大 + 连接森林中的两棵树。

我们考虑这两个东西的复杂度都不低,类似根号平衡的思想,我们考虑降低一个的效率,提高另一个的效率。第 k 小是主席树无疑,连接两棵树则可以使用树上启发式合并。合并时 dfs 暴力修改,用父节点重建每个节点的主席树,更新每个节点的倍增情况。

但是作为一个紫题,其细节还是比较多的。注意到更新倍增数组可以放在 dfs 里。

posted @   revkiru  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示