关于我复习 noip 数据结构这件小事

平衡树与 $LCT$ 待更.

$STL$

关键是会用.比较常用的有 $set,mutiset,priority\ queue,map$ 这几个.

简单说一下 $bitset$ .

1
bitset<MAXN> s;

这样就定义了一个 $bitset$ .默认所有位置都是 $0$ .

可以把它看成是一个长度为 $MAXN$ 的布尔数组,也可以看成是有 $MAXN$ 位的二进制数.

它支持直接调用/修改某一位上的值,

1
2
s[0]=1;
s[1]=1;

也可以用它直接进行位运算,

1
s=s|(s<<10);

一次位运算耗时是长度/系统位数,一般是 $32$ 或者 $64$ .

这个东西就可以把一些只有 $0/1$ 运算的做法给优化 $32/64$ 倍.

原理也很好理解,就是把每 $32/64$ 位压成一个整数参与运算,所以也可以手写它,不过会比较麻烦.

贪心只能过样例 sol

并查集

一定记得要初始化.

优化的方式主要有两种,路径压缩和按秩合并.按秩合并就是把小的合并进大的里面.

如果需要支持退回上一步操作,那么就只能选用按秩合并.

单独使用路径压缩或者按秩合并,查询一次的时间复杂度是 $O(\log n)$ ,同时使用是 $O(\alpha(n))$ .

但大多数情况我们都只写路径压缩,因为很难将它卡到 $\log$ 的级别去.

有些并查集会顺带维护这个块内的其他信息,合并的时候一起合并就可以了.

树状数组

一维的树状数组比起线段树的优势就是代码容易编写与常数较小,而功能远不如线段树全面.

它的优点是可以比较简单的拓展到高维,而二维线段树就已经比较难写了.

如二维平面上的数点/求和,但坐标可能需要离散化.

上帝造题的七分钟

线段树

经典数据结构,使用的方法也很多.

经典应用

线段树的经典应用,维护区间内的信息,并支持单点/区间修改.如维护区间和,支持区间加,区间乘.

本质上只有这三个点是会根据不同的题而产生差别的.

  1. 如何将两个小区间内的信息合并起来得到大区间.
  2. 一个修改操作执行后,区间内的各个信息分别会怎样变化.
  3. 如何合并两个懒标记(如果有区间修改).

如果你发现这三点都能在很短的时间内做到,那么这个信息就可以用线段树来维护.

算术天才⑨与等差数列 sol

排序 sol

序列 sol

动态开点线段树

就是新到一个点的时候,如果没有,你再新建一个节点作为这个节点.

回转寿司 sol

可持久化线段树

又称主席树,因为发明者的姓名拼音缩写为 hjt ,与中华人民共和国前国家主席的缩写一样…

可以存储 $m$ 个版本的线段树信息,要求每次修改都只是单点修改,每颗线段树的管辖范围都是 $[1,n]$ .

考虑线段树的一次单点修改操作,影响到的节点只有对应叶子节点到根节点路径上的所有节点.

这些节点数目是 $O(\log n)$ 的,所以在维护新版本的线段树时,只用新建出这些节点,其它节点沿用上个版本的.

并不用每次都把上个版本的所有节点拷贝出来,因为每个节点只需要知道儿子节点,直接将儿子节点指过去.

显然需要使用动态开点.

经典的使用方法是利用主席树对 $[1,1],[1,2],[1,3]\dots,[1,n]$ 这 $n$ 个前缀建出 $m$ 颗线段树.

这样在查询可减的信息时(如某数的个数),就可以直接用 $[1,R]$ 的线段树答案减去 $[1,L-1]$ 的线段树答案了.

Kth number sol

混合果汁 sol

李超线段树

解决的经典问题是每次可以在平面内添加一条 $y=kx+b​$ 的线段,或者询问当 $x=x_0​$ 时,各个线段中最大的 $y​$ .

如果是先添加完所有线段,再进行若干次询问,可以求出上凸壳, $O(n\log n)$ 解决.

李超线段树维护的是各段区间内 优势线段 的编号.

优势线段 是指,这段区间内能成为最优线段的长度最大的那条线段.

往区间 $(l,r)$ 插入一条线段 $c$ 时,就与当前区间 $(l,r)$ 内维护的优势线段 $s$ 比较.

若这两条线段在 $(l,r)$ 内满足一条完全覆盖了另一条,就直接更新优势线段并返回.

否则,就递归下去,用 $c$ 去更新两个子区间.

因为至少在 $[l,mid]$ 与 $[mid+1,r]$ 这两个区间中的一者, $c$ 完全覆盖了 $s$ ,或 $s$ 完全覆盖了 $c$ .

所以更新一个线段树上的区间的时间复杂度是 $O(\log n)$ ,插入一条线段的总时间复杂度是 $O(\log^2 n)$ .

查询时就在线段上向下走,经过一个区间时,就用这个区间的优势线段来更新答案,每次的时间复杂度是 $O(\log n)$ .

适者 sol

游戏 sol)

吉司机线段树

区间取最值问题

维护一个长度为 $n$ 的整数序列 $a$ ,支持以下 $m$ 次操作:

  1. 区间 $[L,R]$ 内的所有数 $a_i$ 与 $x$ 取 $\min$ .
  2. 区间 $[L,R]$ 内所有数加上 $x$ .
  3. 询问区间 $[L,R]$ 中 $a_i$ 的最大值.
  4. 询问区间 $[L,R]$ 中 $a_i​$ 的和.

对线段树每个区间维护最大值 $mx$ ,最大值的个数 $cnt$ ,次大值 $se$ ,区间和 $sum$ .

询问操作可以定位后直接做.

对于修改操作,若将一个线段树区间对 $x$ 取 $\min$ ,可以分情况讨论:

  1. $mx\le x$ ,无效果,直接返回.
  2. $se<x<mx$ .效果就是将所有 $mx$ 改为 $x$ ,其它不变.新的区间和 $sum’=sum-(mx-x)\cdot cnt​$ 可以直接算出,再打上修改标记,返回.
  3. $x\le se​$ .递归下去,分别修改左右儿子.

复杂度为 $O(n\log^2 n)$ ,但实现效果接近 $O(n\log n)$ .如果没有操作 $2$ ,复杂度就是 $O(n\log n)$ .

最假女选手

历史最值问题

维护一个长度为 $n$ 的数列 $a$ ,支持以下 $m$ 次操作:

  1. 区间 $[L,R]$ 内所有数加上 $x$ .
  2. 区间 $[L,R]$ 内所有数变为 $x$ .
  3. 询问 $[L,R]$ 内数的最大值.
  4. 询问 $[L,R]$ 内数的历史最大值.

修改操作的 $x$ 可能 $<0$ .

尝试将两种修改操作归纳为一种修改操作 $(a,b)$ ,表示先加上 $a$ ,再与 $b$ 取 $\max$ .

那么修改 $1$ 对应的操作就是 $(x,-\inf)$ ,修改 $2$ 对应的操作就是 $(-\inf,x)$ .

考虑两个标记如何合并,假设当前权值为 $v$ ,依次经过 $(a,b),(c,d)$ 两次修改的效果:
$$
\begin{aligned}
v&\to \max{\max{v+a,b}+c,d} \\
&=\max{v+a+c,b+c,d} \\
&=\max{v+a+c,\max{b+c,d}}
\end{aligned}
$$
可以看出就等价于一次修改操作 $(a+c,\max{b+c,d})$ ,于是两个标记就合并为了一个.

为了能查询历史信息,维护一个意义一样的标记 $(a,b)$ ,表示区间内历史的最大增加量 $a$ ,历史最大与 $b$ 取 $\max$ .

将两个标记的 $+$ 定义为两个标记的合并,两个标记取 $\max$ 定义为两个元素分别取 $\max$ .

每次更新标记后,若当前标记为 $x$ ,历史标记为 $y$ ,则让 $y=\max(x,y)$ .

每个结点需要维护当前标记,历史标记,当前最大值,历史最大值 $4$ 个信息.

CPU监控

线段树分治

题目也是让你维护一些信息,每次可以询问,可以执行一种操作,也可以将之前的某个操作撤回.

操作容易维护,但撤回操作不容易维护.

需要将操作,询问都离线下来.将时间轴画出来,那么每个操作只在时间轴上的一个区间内生效.

用线段树给这个区间打上这个操作的标记,维护信息.

数学计算 sol

洞穴勘测 sol

线段树合并

其实就是两颗权值线段树的合并,都使用动态开点.

如果合并到一个位置时,其中一者没有这个位置上的节点,就直接返回另一者.

否则将这个位置的信息合并后,还要递归合并它们的儿子.

树的难题 sol

$kd-tree$

处理二维/高维上点的信息.其实就是一棵二叉树.

注意它是二叉树,所以信息的存储与线段树不同. $kd-tree$ 每个结点存储的是整个子树的信息和自己的信息.

每个点有若干个维度,可以表示为 $(p_1,p_2,p_3,\dots,p_k)$ .

建树时,选择按照当前维度排序后的中点作为根,递归建左右子树,每次进入下一层是更换当前维度.

每个节点需要维护自己的坐标,以及每个维度的管辖范围.

查找平面上最近点是爆搜 + 剪枝,复杂度为玄学.

如果提出一个范围内所有点进行修改/询问,复杂度为 $O(n^{1-\frac 1 k})$ ,其中 $k$ 为维度.

这里的范围是指每一维的坐标都在某个特定的区间中,如二维平面中的矩形.

巧克力王国

弹跳

莫队

经典莫队

需要离线,且无修改操作.

问题是给出 $m$ 个询问,每次询问序列上一个区间 $[l,r]$ 内的信息.

特点是如果维护了 $[l,r]$ 内的所有信息与答案,则可以 $O(1)$ 得出 $[l,r+1],[l,r-1],[l-1,r],[l+1,r]$ 这些区间的信息与答案.

做法是将长度为 $n$ 的序列分块,每 $B$ 个数分为一块.

再将询问排序,排序时以 $l$ 所属块的编号为第一关键字,以 $r$ 为第二关键字.

然后维护当前区间 $[L,R]$ 中的信息,遍历每个询问,不断移动当前区间端点并更新信息,当前区间与询问区间重合时,就得到了答案.

考虑这样做的时间复杂度.

对于同一块内的询问, $L$ 移动 $O(B)$ 次, $R$ 只会往右侧移动,移动 $O(n)$ 次.

对于不同块内的询问, 在每次块改变时, $L$ 移动 $O(B)$ 次, $R$ 移动 $O(n)$ 次.

所以总移动次数为 $O(mB+\frac {n^2} B)$

取 $B=\frac n {\sqrt m}$ 时最优.

再加上给询问排序,整个算法时间复杂度为 $O(n\sqrt m+m\log m)$ .

mex sol

permu sol

带修莫队

就是支持修改的莫队.

做法是将每次询问的时间也考虑进去,形成一个三元组 $(l,r,t)$

排序时加入 $t$ 作为第三关键字.

若移动端点的同时时间跨越了某个修改操作,那么就执行/撤回修改.

为了便于分析,视 $n,m$ 同阶,时间复杂度 $O(n^{\frac 5 3})$ .

树上莫队

分块方式

定义这里的取反是指,若当前节点的信息在贡献中,则除去,否则加入.

若从$(pu,pv)$ 移动到 $(u,v)$ ,则只需对路径 $(pu,u),(pv,v)$ 的点是否包含情况取反, $LCA​$ 不处理.

保存答案前对 $(u,v)$ 的 $LCA$ 取反,答案保存后再将它取反回去.

若有修改操作,则还需记录每个询问的时间.

时间复杂度与序列上的莫队完全一致.

糖果公园

扫描线

最经典的是求矩形面积并.将每个矩形按照 $x$ 坐标排序,从左往右扫过来,看成两个事件,加入和删除.

每次发生了加入事件就去更新答案.

需要注意边界的处理问题,加入和删除操作需要分出前后顺序,根据题目而定.

花火 sol

$cdq$ 分治

一段区间 $[l,r]$ 内的元素两两之间可能产生贡献,要计算出所有贡献.

取区间中点 $mid$ 将区间分为左右两段.

贡献可以分为左边对左边的贡献,右边对右边的贡献,左边对右边的贡献.

前两者可以递归下去处理,所以只需要考虑左边对右边的贡献.

注意每次处理左边对右边的贡献时,时间复杂度必须是与 $[L,R]$ 的长度相关,而不是与整个序列长度 $n$ 相关.

陌上花开 sol

稻草人 sol