专题练习——数据结构

P2056 [ZJOI2007] 捉迷藏

解析

首先距离问题+加修改,我们可以很容易的想到点分树,考虑怎么维护

经过一个重心的最大距离等于最大的两条不相交链之和,因此我们维护两个值,\(nmx_u\) 表示最长的不相交链,\(fmx_u\) 表示子树中到父亲的最长链(对于父亲来说也就是单个子树的最长链)

由于有修改,加上最后的答案,我们要用手写可删堆来维护 \(nmx,fmx,ans\)

至于修改,如果是熄灭就向堆中加 \(dis\),反之就减 \(dis\)

启发

点分树的维护:整个子树的数据,单个儿子构成的子树的数据(也就是子树中所有点到根节点父亲的数据)

手写可删堆

struct heap{
	priority_queue<int> add,del;
	int size(){return add.size()-del.size();}
	void push(int x){add.push(x);}
	void erase(int x){del.push(x);}
	void pop(){
		while(!del.empty()&&del.top()==add.top()) add.pop(),del.pop();
		add.pop();
	}
	int top(){
		while(!del.empty()&&del.top()==add.top()) add.pop(),del.pop();
		return add.empty()?0:add.top();
	}
	int top2(){
		if(size()<2) return 0;
		int x=top();pop();
		int y=top();push(x);
		return y;
	}
}

P8123 [BalticOI 2021] Inside information (Day1)

解析

12操作很简单,就是线段树合并,但由于合并完后又要两个点是独立的,因此要用主席树的合并

难点在于操作3,发现个数是满足一个反向关系的,也就是说,要想包含一个点 \(x\),必须要保证路径上 \(x\) 连出的边是最小的(如图)

图片

那么我们可以想到暴力做法,也就是反向加边,算联通块大小

考虑优化,我们考虑加边的过程,也就是将两个联通块的siz加起来,那么我们可以以时间为坐标,维护每个时间点每个点联通块siz加的数,那么连边就相当于线段树合并,答案就是区间和+1(自身的大小),注意合并时也要+1

启发

处理不同时间的查询,可以以时间为坐标,那么问题也就转化为了区间和

​P9233 [蓝桥杯 2023 省 A] 颜色平衡树

解析

板题,全部相同的条件相当于最大个数等于最小个数(注意不包含0),然后线段树合并即可

P5889 跳树

解析

显然操作是要合并的,但是我们发现难点在于我们不能简单的用一个数来合并,而要用多个数来维护

我们先考虑最基础的:

  • 父亲:\((s>>1)\)
  • 左儿子:\((s<<1)\)
  • 右儿子:\(((s<<1)|1)\)

我们发现这三者都可以表示为 \((((s>>a)<<b)|c)\) 的形式,因此我们想到维护 \(a,b,c\),赋值是显然的

接下来考虑如何合并,其实也很简单,相当于先使 \(s \rarr (max(1,(s>>a_l)<<b_l)|c_l)\),然后再使 \(s \rarr (max(1,(s<<a_r)>>b_r)|c_r)\),可以得到合并方式:\(\begin{cases} a=a_l+max(a_r-b_l,0)\\ b=max(b_l-a_r,0)+b_r \\c=((c_l>>a_r)<<b_r)|c_r \end{cases}\)

剩下的就是普通线段树了

P6186 [NOI Online #1 提高组] 冒泡排序

解析

我们考虑每个数的贡献,就是所有在它之前且大于它的数,我们定义为 \(f_i\)。

考虑一次冒泡排序对答案的贡献,相当于这样的变化

image.png

那么也就相当于中间部分的 \(f\) 减一,而最右边的 \(f\) 已经为0所以不减,因此我们直接从 \(0 \sim n-1\) 遍历 \(f\) 的大小预处理即可

但是还有交换的情况,发现交换一次只有两个 \(f\) 的变化,因此我们将预处理拆开,这样就变成了单点修,区间求和,树状数组即可

启发

逆序对:从单个贡献入手

P5524 [Ynoi2012] NOIP2015 充满了希望

应该是最简单的Ynoi了

解析

由于是赋值操作,那么每个点的取值只有最后一个覆盖到它的赋值操作 \(lst\) 决定,就是区间修单点查

交换就是交换两个点的 \(lst\)

然后考虑求和,一个点要想被计算的条件是 \(l\le lst<t\le r\),(贪心显然),那么就是二维偏序,离散化查询即可

P3792 由乃与大母神原型和偶像崇拜

解析

用类似于哈希的做法,比较区间minmax,区间和,区间平方和是否于连续序列的相同即可

P6327 区间加区间 sin 和

解析

\(\begin{cases}\sin(a+x)=\sin(a)*\cos(x)+\cos(a)*\sin(x) \\ \cos(a+x)=\cos(a)*\cos(x)-\sin(a)*\sin(x)\end{cases}\),直接维护区间 \(\sin,\cos\) 和即可

P11620 [Ynoi Easy Round 2025] TEST_34

解析

首先区间求值是简单的,直接维护一颗线段树套线性基即可

然后我们考虑区间异或,由于直接加复杂度会超的,我们考虑维护差分数组 \(b_i=a_i \oplus a_{i-1}\),那么此时区间修就变为了单点修

又因为 \(a_i=a_l \oplus b_{l+1} \oplus ... \oplus b_i\),因此 \(a_l \sim a_r\) 的线性基就等价于 \(a_l\) 和 \(b_{l+1} \sim b_r\) 的线性基,那么求值又变为区间求

启发

  1. 异或最值:线性基
  2. 异或的区间处理:可以考虑差分后转化为单点处理

P2605 [ZJOI2010] 基站选址

解析

dp是显然的:\(dp_{i,j}=dp_{k,j-1}+cost(k,i)+c_i\),其中 \(cost(k,i)\) 指 \(k \sim i\) 中未被覆盖的 \(w\) 之和

显然要用线段树来维护 \(dp_{k,j-1}+cost(k,i)\)

考虑从 \(i-1 \rarr i\) 的变化: 设 \(st_i,ed_i\) 为能使 \(i\) 被覆盖的最远点,那么从 \(i-1 \sim i\) 时所有满足 \(ed_j=i-1\) 的 \(j\) 会使 \(1 \sim st_j-1\) 的 \(cost\) 加 \(w_j\),直接区间加即可

反思

想成了前缀和作差,没有想到直接维护 \(dp+cost\)

P4587 [FJOI2016] 神秘数

解析

假设现在可以算出 \(1 \sim x\),考虑加入一个数 \(v\),有两种情况:

  • \(v\le x+1\),那么 \(v \sim v+x\) 都能被表示,值域变为 \(1 \sim x+v\)
  • \(v>x\),那么 \(x+1\) 一定表示不出来

那么我们就可以想到一个暴力:从小到大加数直到不能加

我们发现一个一个是不必要的,比如说现在选的最大值为 \(lst\),能表示的最值为 \(now\),那么 \(lst+1 \sim now+1\) 的数都是可以选的,因此我们直接区间求和即可,又因为 \(\sum a_i \le 10^9\),因此总范围是小于 \(10^9\) 的,因此复杂度就是 \(O(n\log n \log(\sum a_i))\)

启发/反思

一直再想用选取方案数来判断是否能表示玩,没有想到先直接暴力的做

P4602 [CTSC2018] 混合果汁

终于做出来道紫的数据结构(喜)

解析

首先肯定二分答案,check也就是看用 \(d_i\) 在 \(mid \sim 10^5\) 的果汁能不能满足要求,用主席树就可以维护这个区间信息

考虑如何check,贪心的想,我们肯定是每次选单价最小的,因此我们就想到以单价为主席树的横坐标,维护体积,花费,然后再线段树上二分即可

P2839 [国家集训队] middle

解析

首先二分答案 \(mid\),然后一个显然的处理是将 \(\le mid\) 的取-1,\(>mid\) 的取1,然后看区间的和,这个可以主席树完成:先默认每个都为1,每次单点修改为-1

正常来讲 \(mid\) 合法的要求是满足区间和为0或1,但由于二分只是确定边界,因此只需要保证满足区间和大于等于0,就可以证明一定有比 \(\ge mid\) 的答案

求区间的最大和是简单的:\(a \sim b\) 的最大后缀+ \(b+1 \sim c-1\) 的区间和+ \(c\sim d\) 的前缀最大值

启发

二分的本质是利用单调性缩小上下界,因此check是确定答案能否在左区间或右区间(这个题就是个很好的例子,对于 \(i,j \ (i<j)\) 来说,可能 \(j\) 能做中位数,而 \(i\) 不能做,但 \(r\sim j\) 有合法中位数证明 \(r \sim i\) 有合法中位数,因此它也是有单调性的)

​P2824 [HEOI2016/TJOI2016] 排序

思路

ODT,每个联通块代表一个升序/降序序列,每个联通块维护一个值域线段树,查询时直接查线段树第 \(k\) 大即可,区间排序与正常的ODT类似,只是加上了线段树合并和分裂

启发

ODT/珂朵莉树

​P4770 [NOI2018] 你的名字

思路

Case 1:\(l=1,r=\lvert s \rvert\)

首先有一个显然的转化:不匹配的数量=总子串数-匹配的子串数

匹配子串数直接做是不好做的,我们考虑在后缀自动机上求解,定义 \(mx_{u}\) 为 \(u\) 节点所代表的子串中能匹配的最大长度,那么总匹配数就是 \(\sum mx_u-len_{fa_{u}}\)

接下来考虑如何更新,我们可以对于 \(T\) 的每个前缀求出能匹配的最长后缀,然后在parent树上跳父亲更新即可(本质就是将最长后缀给拆分了),同时我们每次跳父亲时,如果跳到 \(mx_u=len_u\) 的情况也就不用再跳了(已经最大),这样就可以保证复杂度

求前缀的最大匹配后缀,我们可以给 \(S\) 也开一个后缀自动机,由于SAM的parent树与ACAM的fail树性质类似,我们可以同样的在SAM跳父亲:从算完 \(1\sim i-1\) 后的节点 \(p\) 开始,跳父亲直到有 \(T_i\) 的儿子,最大后缀就是这个节点的 \(len+1\)

Case 2:\(l,r\) 任意

直接将 \(l\sim r\) 的SAM取出来是不现实的,但是我们可以利用原来的SAM达到这样的效果

考虑一个节点,它能在新SAM上存在,当且仅当它存在一个 \(endpos=p\),使得 \(l \le p \le r\),此时它的 \(len\) 变为 \(l\sim r\) 之间的最大长度(感性理解,相当于暴力去除 \(1 \sim l-1\) 和 \(r+1 \sim n\) 留下的直接作新节点)

那么我们就可以对于每个节点开一个线段树维护 \(endpos\) 集合,判断为 \(1 \sim r\) 的最大值是否 \(\ge l\),\(len=min(len,maxpos(1,r)-l+1)\)

线段树的求取直接在parent树上合并儿子即可,注意要持久化

P3332 [ZJOI2013] K 大数查询

思路

线段树套线段树,但是外层是值域线段树,内层是区间线段树

反思/启发

没有想到外层用值域线段树,线段树种类的选取是灵活的

P3242 [HNOI2015] 接水果

初始的想法是表示出包含关系,但是发现并不可行,因为表示一条路径要用 \(O(\log n)\) 个区间,复杂度不过关

我们想到能用一个区间表示的是子树关系,因此我们考虑表示被包含关系

如下图,分为两种情况

  1. \(lca(x,y)=root\):此时一点在 \(x\) 子树中,一点在 \(y\) 子树中
  2. \(lca(x,y) \neq root\):假设 \(dep_x<dep_y\),此时一点在 \(y\) 子树中,一点在 \(z\) 子树外(\(z\) 为 \(x \rarr y\) 上最近一点)

dc73daf19464c87a05624577e74c36c8.jpg

(\(z\) 处笔误了,应为 \((1,st_z-1) \lor (ed_z+1,n)\))

这时就变为了二维数点问题:求包含一个点的矩形中权值第 \(k\) 大的点,整体二分即可

反思/启发

  1. 正难则反,包含于被包含的转换
  2. 整体二分

P12685 [国家集训队] 排队 加强版

思路

动态逆序对板子,每次交换时减去 \(x,y\) 的贡献,交换后加上新的 \(x,y\) 的贡献即可,注意常数

P4093 [HEOI2016/TJOI2016] 序列

二维数点优化dp转移

设所有变化中的最大最小值为 \(mn_i,mx_i\),那么能从 \(i \rarr j\) 的条件是 \(a_j \ge mx_i,mn_j \ge a_i\),直接树套树维护二维区间的最大值即可

P5621 [DBOI2019] 德丽莎世界第一可爱

思路

四维偏序,考虑从三维偏序衍生,三维偏序的步骤:

  1. 将第一维排序
  2. 递归处理子问题,当前函数中处理 \([l,mid]\) 和 \([mid+1,r]\) 之间的偏序对
  3. 分别将 \([l,mid]\) 和 \([mid+1,r]\) 按第二维排序,这里虽然将第一维顺序打乱,但保证了 \(\forall i \in [l,mid],j \in [mid+1,r]\ , a_i \le a_j\),因此可以处理
  4. 用树状数组维护第三维

四维偏序中的后三维可以按照这个方式来处理,但问题在于第一维,我们考虑再套一层分治,像上面的 \(2,3\) 步一样,单次分治中只处理跨过 \(mid\) 的关系,这样我们只需要给每个数打上一个属于左边还是右边的标记,就可以维护第一维,复杂度 \(O(n \log^3 n)\)

启发

对cdq分治的进一步思考,怎么在第二维有序的情况维护第一维:分治解决,每次只保证 \([mid+1,r]\) 一定大于 \([l,mid]\),而不是真正的有序

P5445 [APIO2019] 路灯

思路

依旧是正难则反,考虑每一时刻那些区间是可行的,显然对于一个极长的全 \(1\) 序列 \(l,r\),所有 \(l\le x \le y \le r\) 都可行,每一时刻就是这些区间的并集

这样显然复杂度不过关,考虑优化

我们发现,单次修改最多只会造成 \(3\) 个区间的变化,因此我们树套树时维护三个值 \(tim\):未被删除的区间最近一次加入时刻之和,\(sum\):已经被删除的区间存在时间总和,\(cnt\):未被删除的区间个数,那么 \(t\) 时刻的答案就等于 \(sum+t*cnt-tim\),至于存在区间的维护用set即可

启发

区间问题的一种想法:二维数点

对于时间时刻的拆分

P4690 [Ynoi Easy Round 2016] 镜中的昆虫

好题,lxl牛逼

思路

从简单到困难考虑

无修改,区间查

记录对于一个数 \(a_i\),记录 \(b_i=max_{j=1}^{i-1} j\ , a_j=a_i\),答案就是区间内 \(b_i < l\) 的个数,扫描线+树状数组即可

单点修,区间查

直接修改 \(b_i\) 即可,用链表维护

区间修,区间查

看似复杂度是 \(O(nm)\) 的,但实际上对于一个连续颜色段来说,只有左端点的 \(b_i\) 由整体的颜色决定,其余的都是 \(b_i=i-1\),因此我们每次只需要修改颜色段的端点即可,由于一个颜色段最多删除一次,每次操作最多只会增加一个段,修改次数是均摊 \(O(n+m)\) 的

启发/反思

颜色段均摊有两层含义,都是建立在存在区间赋值(推平)操作的基础上。

  1. 随机数据:任意时刻的颜色段个数期望 \(O(\log_2n)\);
  2. 非随机数据(本题):每次推平时访问的颜色段个数为均摊 \(O(n)\)。

P3165 [CQOI2014] 排序机械臂

思路

区间查第 \(k\) 小显然是不可做的,但是由于本题的性质,第 \(i\) 小相当于 \([i,n]\) 中的最小值

注意有题目对于大小相同的数的要求,需要先赋一个排名

启发/反思

没有想到最小值的转换

P3987 我永远喜欢珂朵莉~

思路

整除的均摊复杂度是 \(O(n \log n)\) 的,难点在于如何找出区间内被 \(x\) 整除的数

发现约数的个数并不多,因此可以对每个约数开一颗平衡树,每棵树内维护被 \(x\) 整除的数,区间操作直接遍历整除即可,同时将不能被整除的删去

但是还有一种更优的做法:使用路径压缩的链表/并差集实现平衡树的功能

启发

并查集/路径压缩思想的应用:可删链表的维护

​

​

posted @ 2026-10-30 14:55  Rainesxiao  阅读(3)  评论(0)    收藏  举报