数据结构/树据结构 part1
P10834 [COTS 2023] 题 Zadatak
这道题挺有意思的,我们首先发现这题之和黑色有关系,然后合并时类似区间的布尔运算。
所以想到线段树合并。 然后就能做了。
trick:标记的维护很有意思,在查询的时候我们如果其中一棵线段树的当前节点没有子节点,则无需 pushdown,在另一棵线段树的对应节点处理标记并返回该节点即可,答案在最深一层节点处计算。
P11210 『STA - R8』强制在线动态二维数点
好题,这题要维护一个一个区域中最接近对角线的点,如果我们无脑直接把矩形限制变成区间限制会发现答案错了

你会发现你把 Q 的答案算上了,想想我们呢怎么剔除这个东西,我们从\(P\to r\) 就可以了对吧。
trick:所以我们先找到行 \([l,r]\) 最小的 \(x\) 在查询 \([x,r]\) 最小的 \(y-x\) 就可以了,用 multiset 维护删改。
P10759 [BalticOI 2024] Jobs
很好的启发式合并,步步递进,很有意思。
首先设置一个 dp 表示在这个子树赚钱最小要有多少钱。思考怎么合并,我们肯定贪心的来选择需要钱最少的对吧。
那么我们就会在一个点向他的儿子跳最小值,这样一直操作直到回本,你发现这是 \(O(n^2)\) 的,问题在于你重复跳到了你计算过的节点。
所以我们把计算过的子树打包,加上在最小赚钱的条件下最少能赚多少。然后把这个子树没有计算的点和当前树的节点合并,这样就不用重复跳点了。
我们用启发式合并套路的维护合并就可以了。
P9984 [USACO23DEC] A Graph Problem P
个人认为非常 nb 的题目。这个题目要求我们求出每一个点出发做 prim 算法的遍历顺序。
先转化成最小生成树,我们发现对于最小生成树上最大的边,其被分成了两部分,每一部分的后半段其实都是一样的,因为都要经过这条边然后遍历另一边。
所以发现了重要的性质:有很多部分是相同的,我们发现这个部分是子树加法和乘法。思考怎么把树上变成区间。
trick:最小生成树上的区间子树操作考虑 kruskal 重构树的dfs序列。
然后做普通的线段树操作就没了。
P6845 [CEOI 2019] Dynamic Diameter
同样是一道 nb 题,我们想一下怎么动态求直径,我们先经典的把直径拆成两条到根的链减去 lca的深度的 2 倍。
考虑把这棵树拍成欧拉序。那么两个点的 \(d_{\text{lca}(u,v)}\) 其实就是 \([\min\{L_u,L_v\},\max\{R_u,R_v\}]\) 中的最小值,其中 \(L\) 和 \(R\) 表示一个节点在欧拉序列中第一次和最后一次出现的位置。
再考虑到它前面的符号是负号,所以你没取最小值而是更大一些的数会使答案更劣,不会这么做。同样的,如果我们的左端点不是 \(\min\{L_u,L_v\}\),右端点不是 \(\max\{R_u,R_v\}\),也不会取到更大的答案。
因此我们只需要求出: 对于一个序列 \(\{a\}\),\(\max_{i \le j \le k}\{a_i+a_k-2a_j\}\),这个可以用很朴素的线段树维护。
P5631 最小mex生成树
先说为什么不是最大生成树,因为含义不同,所以确实不同。
接下来想到枚举答案,我们发现判断答案是否成立就是判断去掉了这个边权 \(-1\) 的边,剩下的边能不能构造成一棵树。
那么就我们发现这个操作有很多的删边加边操作,换个方向就是要我们动态维护连通性。
我们发现每个边只会删加 \(1\) 次,我们用可撤销并查集就可以了。
优秀的实现(类似线段树分治操作):
void solve(int l,int r,int x,int y){
if(l==r){if(siz[find(1)]==n){cout<<l;exit(0);}return;}
int tp=st.size(),pos=y+1,mid=l+r>>1;
while(pos>1&&a[pos-1].w>mid) pos--,merge(a[pos].u,a[pos].v);
solve(l,mid,x,pos-1);del(tp),pos=x-1;
while(pos<m&&a[pos+1].w<=mid) pos++,merge(a[pos].u,a[pos].v);
solve(mid+1,r,pos+1,y);
}
SP10628 COT - Count on a tree
树上主席树板子,考虑一下我们把链拆成 \(u,v,lca(u,v),fa[lca(u,v)]\) 这四个点到 \(root\) 的链。
我们看下怎么制作每一个点到 \(root\) 的主席树,dfs 的时候把当前点连接到父亲节点的主席树就可以了。
P4093 [HEOI2016/TJOI2016] 序列
cdp分治的处理顺序原来是可以不固定的啊。
记 \(f[i]\) 为以第 \(i\) 项结尾的子序列最长长度。
则有转移:\(f[i]=\max_{j<i}(f[j])+1\),同时还要满足 \(maxval_j\le a_i\) 和 \(a_j\le minval_i\)。
其中 \(maxval_i\) 表示第 \(i\) 项最大能变成的值,\(minval_i\) 表示第 \(i\) 项最小能变成的值。
按照项从小到大转移,形成了天然的时间顺序,同时还要满足两个偏序限制。
算上时间顺序,这是一个三维偏序问题,用 CDQ 分治 + 数据结构(我用了树状数组)就能解决。
细节/trick:为了得到正确的每个点的 \(f\) 我们要用先序遍历 cdq。
P3979 遥远的国度
换根操作板子。分成 3 种情况:
-
根就是自己:那么查询所有的点
-
如果自己是根的祖先,那么查询除了自己到根的那个儿子子树的所有节点
-
否则就查询自己的子树
一般的换根操作其实还多一种情况,参见 浅谈树链剖分 "换根操作"
P3302 [SDOI2013] 森林
其实就是 Count on a tree 这个题有了一个合并两棵树的操作。
我们假如要合并,发现操作可以只修改一棵树,所以我们启发式合并就做完了,navie 题。
P3293 [SCOI2016] 美味
暴力能过绷不住。
看到异或先拆位,然后我们就可以看到询问是动态区间的区间大小询问,用主席树直接维护就可以了。
P3250 [HNOI2016] 网络
trick:用两个堆分别维护添加和删除操作,如果两个堆数字相同就把这个数字删了。
用线段树维护,空间单 log 时间双 log。
P2617 Dynamic Rankings
动态求区间第 k 小,带修改。
先考虑不带修改,就是一棵普通的主席树。然后我们看到修改实际上改变了所有后缀的线段树。
因为主席树实际上是维护了线段树的前缀和。所以查询和修改的时间比例是 \(1:n\).
考虑用树状数组的房市优化这个前缀和,我们把每个主席树建在树状数组的范围内就好了。
P12765 [POI 2018 R3] 三座塔 2
神迷题/诈骗题 问题是给一个三色序列,求最长的三色不同序列长度。
结论:这个序列的 开头/结尾 一定在最开头/最结尾的 3 个点里面。证明自己枚举就可以了。
这个性质在四色是,我看了一下好像不成立。
P11433 [COCI 2024/2025 #2] 三角 / Trokuti
做过在考场上还没打出来:/
考虑随机化,注意判断,死这里了。
P10989 [蓝桥杯 2023 国 Python A/Java A] 等腰三角形
给定一个包含 \(n\) 个数的序列 \(A_i\),每次操作可以选择其中任意一个数将其 \(+1\) 或 \(?1\)。
我们要让这个序列满足能够从中任选三个数,这三个数对应长度的三条边总能组成一个等腰三角形。问最少需要多少次操作才能让序列满足条件。
发现这个问题只有可能是一个数或者两个数,一个数的话显然是中位数。
思考两种数字的情况,首先区间肯定被分成两个部分,一部分变成一个数字。
我们枚举这个断点,然后两侧的中位数一定是能让修改最小的。
若这两个数不合法,发现这个东西对于·每一个区间都是单峰的。三分就可以了。
corner case:若其中一个数字只有一个,那么不需要考虑 \(x+x<y\) 的限制了。
P10795 『SpOI - R1』Lamborghini (Demo)
神题,发现有个最小值的限制,考虑先按照 t 从大到小排个序,然后加点。
每次加的点一定满足限制 1,我们看和这个点相邻的点,如果这个数字被访问过,那么就可以计算答案。
看下怎么搞限制 2,我们可以搞一个动态开点的线段树,加点的时候一个区间选大于等于这个点的个数,另一个找小于的,同理相反一下就好了。
动态的加答案刚好是不重复的,类似点分治的加法。
P5227 [AHOI2013] 连通图
线段树分治可以解决 " 操作在若干个时间区间内有效 " 的问题。考虑把删边转化成这个东西,存在的区间。
然后把存在与否挂到线段树上,用可撤销并查集维护一下就好了。
我不是很会线段树分治,这种题要多做一点。
P8476 「GLR-R3」惊蛰
神题,我们先设计 dp,发现转移在维护后缀 min。
发现这个 dp 转移可以用区间赋值,区间乘?,区间加维护,保证单调下二分即可。
其实就是分段函数的特殊性质罢了。
P8026 [ONTAK2015] Bajtocja
发现时间复杂度是允许我们枚举答案的,那我们就只是需要看答案是否合法就行了。
我们发现如果成为答案那么他们在同一张地图中的并查集 fa 都是一样的。
我们考虑给每一张图的每一个并查集维护一个随机权值,那么答案一定在所有所在并查集和相同的值里面。
考虑维护并查集,因为合并依旧只和最小的集合个数有关,所以启发式合并就可以了。
P7735 [NOI2021] 轻重边
典典题,我们把修改的链附上新颜色,重边两侧颜色一样,而轻边不同,维护颜色块数的线段树就可以了,很好写。
trick:维护相邻节点的修改,我们考虑用新颜色维护颜色段。
P7477 「C.E.L.U-02」划分可重集
看到两个集合其实就是 2-SAT 了,考虑怎么建边,我们直接按照这玩意修改显然是不行的。
考虑用主席树维护建边,很麻烦,要维护 4 棵主席树,注意建边中间还要隔一个点,要不然不对。
好像也可以用 cdq 来建图,因为是偏序关系。
P7476 「C.E.L.U-02」苦涩
还不错的题,就是线段树套一个堆,就没了,标记下川比较有意思。
P6247 [SDOI2012] 最近最远点对
基本上都是乱搞,正解是分治 x 坐标,然后继续分治中间部分,很难写。
乱搞的话,稍微合理的是 kd-tree,还有随机旋转然后检查相邻的一些点。
P5157 [USACO18DEC] The Cow Gathering P
比较好玩的性质题,首先有 2 个性质:
-所有答案都相互连通
-可以通过贪心找到一个合法点
然后就先做一遍贪心(topu),然后 dfs,就可以了。
P5025 [SNOI2017] 炸弹
挺多解法的,我们可以维护到左侧和到右侧的单调栈,然后就能直接维护了。
一般的解法是用线段树优化建图然后跑一边 tarjan。
好像还可以用一个性质,转弯次数不超过 log 次,用二分来解题。
P4795 [BalticOI 2018] 基因工程
随机赋值好题
发现数据范围是允许我们枚举的,那我们就看一下,怎么快速判断两个串是否合法。
我们可以快速把列元素找出来,就可以快速比较元素不用个数了,显然加个权就不容易被 hack了。
所以随机每一行的权值。
当时写的东西:
1.找暴力n^3
2.发现瓶颈在比较->如何快速比较
3.想到HASH和这个题的特殊性质,只有四种颜色
4.想到随机权值,和差k的性质
5.想到随机串权值+颜色比较
P4768 [NOI2018] 归程
谁能懂一遍 A 的救赎感。因为有 ksk重构树 所以放在了这个板块。
首先我们可以发现答案是先开一段车到距离 root 最近的点,然后走路。
距离 root 最近显然可以用 spfa dijkstra 来写,问题是怎么处理每个点能到达的点位。
我们发现雨水这个限制是最小边权,所以你的路径应该在最大生成树上,接着惯性想到了 ksk重构树。
ksk重构树有一个性质,祖先中的点权是这个点到原树上所有点的可能的最小值,所以我们可以在构建重构树的时候把最小子树到 root 距离处理出来。
然后我们还可以去 dfs 重构树然维护 st表 来使得可以找到最高的祖先。查询就直接跳到这个祖先就好了。
P4719 【模板】动态 DP
不是很喜欢 ddp。
ddp 用来维护有修改的树形dp,其维护的东西满足交换律,这种东西之后搞吧,难难难。
P4556 [Vani有约会] 雨天的尾巴
给一棵树,每个点有不同颜色,有路径添加颜色,问点上最多的颜色是什么。
先把链拆开,拆成 4 条到 root 的链,然后查询的时候就用线段树合并,离线下来。
如果在线的话用树链剖分,就不用线段树合并了。
P4587 [FJOI2016] 神秘数
思维题,最主要的就是找性质,发现答案可以用主席树维护,然后就做完了,挺难想的。
P4585 [FJOI2015] 火星商店问题
可撤销trie,很恶心。
用线段树分治维护出现时间的修改和询问,用可撤销trie维护答案,也可以用tag来维护trie。
P4559 [JSOI2018] 列队
线段树动态开点+非常规查询。
先想到肯定是一群人往左边跑,另一群向右跑,而且两部分人不相间。
考虑在这个情况下线段树二分,二分左右跑的分界线,注意人数也会把 k 变化就没了,挺简单的。
P4479 [BJWC2018] 第k大斜率
考虑二分答案,发现用表达斜率可以转化成二位偏序,用树状数组维护就可以了。
P4473 [国家集训队] 飞飞侠
很容易想到用线段树优化建图然后跑 tarjan,具体就是每一个斜列一个线段树,但比较难写。
我以我用分层图写的,就是转化为在这个点还能走几步,一个点自己连成环,对于别点向步数 \(-1\) 连边。
P4390 [BalkanOI 2007] Mokia 摩基亚
三位偏序板子,我们发现二维的点,围成矩阵,其实就是两维限制了,再来一个时间就是三维的。
trick:二位数点可以用 cdq 解决。
P4331 [BalticOI 2004] Sequence 数字序列
这图可以用 solpe trick 来维护,但是这是数据结构,所以我们来讲左偏树。
先把点 \(-i\) 让问题变为不下降子序列,这样好写点。
我们发现最优答案一定是中位数,所以我们直接维护每一个单调递增区间的答案。
这个东西,变大不管,变小直接找中位数。
P4247 [清华集训 2012] 序列操作
大难题,选数我们可以暴力维护选某个个数的数字和,这玩意是有可乘性的,合并直接乘起来就行了。
先看操作二:区间取反,这个很简单,取奇数个数的 tag 取反,偶数个 tag 不变。
操作一很麻烦,要推式子,我们考虑组合意义: 其实就是二项式定理,直接修改 tag 就行了,\(c<20\) 所以常数还是很大的。
AT_joisc2014_c 歴史の研究
回滚莫队
-
问题可以莫队。(询问可以离线,不带修改)
-
区间伸长的时候很好维护信息
-
区间缩短的时候不太好维护信息(如最大值,删除以后不知道次大值是多少)
方法是伸长时把答案记下来,查询的时候回滚到之前的段然后更新。
AT_agc016_e [AGC016E] Poor Turkeys
时间回溯 trick,正难则反,我们把问题倒着看,枚举答案。
P4219 [BJOI2014] 大融合
线段树分治,因为查询类似断边然后查询连通块大小然后再次合并。
其实就是出现与否,所以用线段树分治+可撤销并查集维护。
也可以离线操作+树链剖分,维护起来比较复杂,但简化后就是一个差分数组。
SP16549 QTREE6 - Query on a tree VI
考试考到同样的trick没写出来,啊啊啊:/
其实就是维护能向上到达的最浅的祖先节点,之后就是链上求和,点权是轻儿子的子树连通块和。
考试的是链上修改,这个是单点修,其实都是一样的,两种颜色 tag 取反就好了。
SP11985 GOT - Gao on a tree
好像写过这题题解?本质上就是比正常线段树多一个颜色限制,但是信息总量不变,所以动态开点做线段树就好了。
注意常数,我们用 unordered_map 开颜色的树。
SP2916 GSS5 - Can you answer these queries V
最大子段和但是有左右区间限制,其实没啥,分类讨论就好了,但是类数挺多的。
P4211 [LNOI2014] LCA
又是经典trick:我们把链拆成到根的链,我们比较懒,所以考虑离线。
先按照编号排序,询问拆开城差分,即把询问拆成 \(ans_{[1,r]}-ans_{[1,l-1]}\)
然后维护链上加法就好了。
P4198 楼房重建
很有意思的题目,先转化斜率递增串的长度(注意不是最长递增串)。
考虑怎么维护,我们维护区间最大最小值,然后把这个东西递归。
具体一点,为了合并左右区间,其实只和左侧最大点和右侧区间,然后这个问题就变成了子问题。
我们就可以知道长度了,类似线段树套线段树查询。
P4180 [BJWC2010] 严格次小生成树
先讲一下简化版,非严格次小生成树。
先建出来最小生成树,然后考虑加一个点,会形成一个环,这个环就是 lca 和连接的两个点链上形成的环。
然后在环上找到最小值,用新边替换这条边。找到其中的最小值。
看一下为什么这个东西不行,应为有可能答案相同,所以在找最小值时还要同时维护严格次小值。
用线段树+树剖要好一点。
P4169 [Violet] 天使玩偶/SJY摆棋子
拆方向trick:当我们把答案拆成左上,右上,左下,右下四个方向。
我们发现这是一个显然的三位偏序, 直接用 cdq 维护相邻最小值就好了。
弱化版就是直接dp(但是横向/纵向可能带权,这样cdq就搞不了了)。
P4107 [HEOI2015] 兔子与樱花
简单贪心,我们考虑怎么样搞最优,显然是拆下最小的链,不断这么搞就行了。
P4036 [JSOI2008] 火星人
遇到这种平衡树板子,假如不能用 set 维护,又不想写 fhq。
那么就用vector解决。因为vector 1s可以跑 \(10^{10}\) 次计算(insert),很神秘。
P3925 aaa被续
比较简单,问题就是子树从大到小排序的值乘上一个系数。
这个比较有意思,排序后考虑每个点的贡献,就是祖先剩下的儿子的值的和。
所以维护链上和,链上减就好了。
P3586 [POI 2015] LOG
让我对树状数组的理解提升了一个维度。
问题:解决把全局大于 \(x\) 的数字变成 \(x\) 求全局和。
我们用树状数组维护大于这个数的数量和这些数字的和就可以维护了,都是可以用树状数组维护的。
离线是因为空间开不下。

浙公网安备 33010602011771号