zoukankan      html  css  js  c++  java
  • 2021暑期集训小记 · 数据结构篇

    7.8

    线段树与平衡树应用

    P4198 楼房重建

    询问有多少个 (a_i)([1,i]) 的前缀最大值。直接用线段树搞,考虑如何合并两个已经算完的区间 (A,B)。进行分类讨论(设 (A,B) 中最大值为 (mxa,mxb)(B) 的两个子区间的最大值为 (mxb1,mxb2)):

    1、(mxage mxb)(B) 中全被盖住,答案就是 (A) 的答案

    2、否则,若 (mxb1>mxb2),显然只需要递归左半边。

    3、(mxb1<mxa<mxb2),只递归右半边

    3、(mxa<mxb1<mxb2),此时 (mxa) 对右半边没有影响,只递归左半边

    每次修改 (log n) 个节点,每次至多向下递归 (log n) 次(只走一边),复杂度 (O(nlog^2n))

    P4036 [JSOI2008]火星人

    求最长公共前缀可以用 (hash)+二分。问题就是动态维护区间的 (hash) 值,可以直接用平衡树搞(以位置为键值,按 (size)(split)),因为用 ([l,mid],[mid+1,r])(hash) 值可以轻松算出 ([l,r]) 的值。

    P5278 算术天才⑨与等差数列

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

    后一个是前一个的弱化。若区间 ([l,r]) 可以组成公差为 (k) 的等差数列,需要满足几个条件:

    1、最小值和最大值的差一定,这个很好维护

    2、两两之差都是 (k) 的倍数。先差分,令 (b_i=a_i-a_{i+1}),然后用线段树维护 (b_i) 的区间 (gcd) 就可以了

    3、没有相同的数。设 (last_i) 表示上一个和 (a_i) 相同的值的位置,对每种值开一个 (set) 动态维护 (last),然后再用线段树维护 (last) 的区间最大值,没有相同的数等同于 (max{la}<l)

    P6617 查找 Search

    和上一题差不多。但不能对每一个 (i) 都维护 (a_i+a_x=w) 的最大的 (x),因为这样每次修改量不是 (O(1)),(和 (x) 相连的不止一个)

    我们需要回答的只是 ([l,r]) 内是否存在,那么可以贪心的维护关系。对于所有 (pos),都只和前后的最近的 (x)(1) 条边(即对于 (pos)(x) 双方来说,对方都是最近的)。同样可以用 (set) 和线段树维护

    李超线段树

    提到这个东西了,复习一下吧。对于每个区间,只记录最高点最高的那条线段 (x)。插入一条线段 (y) 时:

    1、若在当前区间 (x) 完全优于 (y)(return)

    2、(y) 完全优于 (x),把 (x) 换成 (y)(return)

    3、若有交点,横坐标为 (p),若 (p<mid),留下右边那条,左边的往左子树递归,反之亦然。这样每次只递归一边,复杂度和普通线段树相同,(O(nlog n))

    查询 (pos) 的答案时,对线段树上 (path(root,pos)) 路径上 (log n) 条线段取最值即可。

    P7447 [Ynoi2007] rgxsxrs

    对值域倍增分块,第 (i) 块为 ([b_i,b_{i+1})),共 (log_ba_i) 块,对每块建一棵线段树维护到 (b_i) 的最小距离 (s_i)。对每块分别处理修改。分类讨论计算时间复杂度

    (s_ige x),那么所有数都不会掉出第 (i) 块,直接在线段树上正常区间修改,(O(mlog_ba_ilog_2n))

    (s_i<x)(x) 不在第 (i) 块中。此时距离 (<x) 的点将往下掉,在修改时,若线段树上当前区间最小值 (<x) 就继续递归,否则 (return)。若有 (cnt) 个点下落,则遍历线段树的复杂度为 (cntlog n),而 每个数最多下落 (log_ba_i) 次,所以 (sum cntleq nlog_ba_i),这部分是 (O(nlog_ba_ilog n))

    最后,若 (x) 在第 (i) 块中,直接在线段树上暴力修改,由于是 (b) 进制,(a_j,xin[b_i,b_{i+1})),每个数在每块中最多暴力 (b) 次就下落了,复杂度 (O(nblog_ba_ilog n))

    (b=8/16) 较优。由于特意卡了空间,还要一个小 (trick):先对 ([1,n]) 分块,块大小为 (log _ba_i),然后在块上建线段树(以块为叶子),块内暴力,时间复杂度不变,空间优化为线性。

    7.9

    树上问题

    P4219 [BJOI2014]大融合

    虽然是动态加边,但可以离线。所以先把整棵树完整的建出来,然后用并查集和树剖维护当前子树大小,加减乘一下就好了。

    P4211 [LNOI2014]LCA

    考虑最最暴力求 (LCA):先把 (x) 一路向上跳到根,经过的地方标记为 (1),然后再跳 (y),第一个撞到的 (1) 就是了

    本题用的就是这种“暴力”思想。先把原式差分,就成了计算 (z)([1,x])(LCA) 的深度和。离线询问,把 (z) 扔到 (x)(vector) 里。当 (xRightarrow x+1),先把 (path(root,x)) 上的点 (+1),然后再处理询问,每次查询 (sum of path(root,x)) 即为答案

    树上最远点对

    每次给出 ([l_1,r_1],[l_2,r_2]),从两个区间中分别选出 (x,y),求 (max dist(x,y))

    用线段树维护区间的“直径”,合并两个区间的时候只需要枚举四种新的方案(以原直径端点为新端点)。计算一次答案时先在线段树上询问 ([l_1,r_1],[l_2,r_2]),然后再合并它们两个

    Bzoj4771 七彩树

    方案Ⅰ:一种比较暴力的做法,复杂度略高。对每个节点维护一个平衡树记录所有颜色的最浅深度,再用个 (map) 之类的维护当前子树中 (x) 的最浅深度,加入一个点和询问显然很好处理。然后对于 (u),将其儿子的信息进行启发式合并。大概是 (nlog^2n)

    方案Ⅱ:高效的做法。对每个点弄两个线段树,一个维护 (s_i) 表示最浅深度为 (i) 的有几种颜色,另一个维护 (mn_i) 表示颜色 (i) 的最浅深度。而线段树可以直接合并,且复杂度为 (O(nlog n)),(共 (nlog n) 个节点,每 (merge) 一次减少一个有用的节点,(O(nlog n))

    P5314 [Ynoi2011]ODT(弱化版)

    一棵边权为 (1) 的树,支持:

    1、把链上所有点权 (+k)

    2、查询距离 (x) 距离 (leq 1) 的点中点权第 (k)

    方案Ⅰ:进行根号分治,对于度数 (>sqrt{n}) 的至多 (sqrt{n}) 个点弄个数据结构,其它的小点暴力

    方案Ⅱ:轻重链剖分,在每个节点维护一棵平衡树,修改时:对于轻儿子(链顶),直接暴力更改;对于重链,在线段树上修改。查询的时候所有轻儿子已经搞好了,只需要再加入 (x,fa_x,heavy son_x)。总复杂度为 (O(nlog^2n))

    7.10

    分块与莫队应用。顺序有点乱

    P4396 [AHOI2013]作业

    (brute):莫队+树状数组/线段树。(O(nsqrt{m}log n))

    优化:询问次数为 (m),修改次数 (nsqrt{m})。它们的次数并不平衡,可以使用分块来平衡复杂度,即 (O(1)) 修改,(O(sqrt{n})) 询问。这样 (log) 就没了

    P4689 [Ynoi2016] 这是我自己的发明

    先用 (dfs) 序弄到序列上,发现换根后新子树 (x) 最多可以用两段原 (dfs) 序来表示,依旧可以上莫队。

    现在就是要计算 (f(l_1,r_1,l_2,r_2)),二维差分(还是叫前缀和?)一下,就成了求 (f(1,x,1,y)),只有两个变量,直接莫队搞

    AT1219 歴史の研究

    多次询问区间带权众数。发现由于所有数出现次数之和为 (n),那么答案只有 (n) 种可能,离散一下。然后就和第一题一样了,用分块平衡去掉 (log)

    P3245 [HNOI2016]大数

    P3604 美好的每一天

    两个差不多的题。莫队算法的一个端点每移动一次,跟原本区间答案的差别在于前缀/后缀子串。

    对于第一题,先特判 (2,5),然后弄个取模的前缀和 (s_i),假设变化的端点是 (x),那么答案的变化就是区间中 (s_x) 的数量

    对于第二题,如果能构成回文,说明最多只有一种字母有奇数个。“奇偶”让我们想到用异或来处理,给第 (i) 种字母赋值为 (2^i),处理区间前缀和,就和上一题差不多了。区别在于因为这题有两种情况((0) 个或 (1) 个奇数),如果为 (1) 还要枚举一下是哪个,复杂度多了个 (26) 的系数

    P5072 [Ynoi2015] 盼君勿忘

    对于一个固定的区间 ([l,r],len=r-l+1),数字 (x) 出现了 (c) 次,那么 (x) 的贡献为 ((2^{len}-2^{len-c})x)。如果模数不变,在移动端点的时候,变化的 (x) 特殊处理,其它的直接 ( imes 2) 就完了。而模数变化导致不能直接继承之前的答案。

    对出现次数分治。若出现次数 (ge sqrt{n}),最多 (sqrt{n}) 个,暴力计算(不用维护当前区间中 (gesqrt{n}) 的有哪些,只要在整个序列中出现次数 (ge sqrt{n}) 就可以暴力);否则记录出现次数为 (i) 的数字之和 (s_i),放在一起算。实现 (O(1)) 修改,(O(sqrt{n})) 询问的平衡。

    BZOJ4358 permu

    如果直接用普通莫队,难以 (O(1)) 实现删除,所以使用 “不删除(回滚)莫队" 。如果只有插入操作,可以用链表来维护当前的每个连续段((x) 向左、右指向包含 (x) 的连续段最远延续的位置),而答案也满足单调不降,不断取 (max) 即可。

    回滚莫队大致方法:先分块,排序方式不变,对于每次询问的 (l) 先把指针指在 (l) 所在块的右边界,然后向左移动,而 (r) 在同一块中单调,一路向右扫,只加不减。先移 (r) 再移 (l),用栈存储 (l) 移动的变化,每次询问完后撤销 (l) 的操作

    Codechef QCHEF

    和上题相反,这题难以快速插入。那么使用 ”不插入莫队“,先把链表建好(上一个和后一个相同的值在哪),删除的时候可以 (O(1)) 得到前驱后继并更改。

    经典问题1(CF785F)

    多次询问 ((l,r):min{|a_i-a_j|},lleq i<jleq r)

    当值域较小时:和上一题相似,使用 "不插入莫队"。但这样答案会不断增加,而求的是最小值。再用值域分块来维护答案的变化,和第一题一样。

    但值域较大时,无法值域分块。

    方案Ⅰ:直接分块处理。(sqrt{n}) 为一块,然后进行块内排序,设 (b_i)(i) 所在块编号,(L_i,R_i) 为第 (i) 块的左右边界,先处理 (f_{i,j}) 表示从 ([j,b_i-1])(b_i) 块前缀 ([L_{b_i},i]) 这两部分中分别选一个的最小答案,(g_{i,j}) 类似(向后),(s_{i,j}) 表示块 ([i,j]) 能选出的最佳答案。因为块内有序,这些都能 (O(nsqrt{n})) 搞完。然后处理询问:

    1、如果 (l,r) 在同一块,块内已经有序了,直接把 ([l,r]) 的取出来暴力计算相邻两个的差

    2、不在同一块,分成 左边一小段、中间几段整块的、右边一小段 三部分。如果 (i,j) 有一个在中间,直接调用 (f,g,s) 就得到答案了;如果 (i,j) 都在两边,还是根据单调性暴力扫。

    复杂度 (O(msqrt{n}))

    方案Ⅱ:固定 (j),考虑选取 (i<j)((i,j)) 作为最优解对询问的贡献。以 (a_i>a_j) 为例,(a_ileq a_j) 相同。建立权值线段树,可以快速找到满足 (p<j,a_p>a_j) 的最大的 (p),但如果暴力一个一个往前找 (p) 并没有优化效果。若当前找到 (p),下一个找到的 (p') 如果有用(可能成为答案),需要满足 (a_{p'}-a_j<a_p-a_{p'}Rightarrow a_{p'}-a_j<frac{1}{2}(a_p-a_j))(a_p-a_j) 每次都至少减半,设值域为 (N),那么至多只找到 (log N) 个有用的区间 ((i,j)),可以写成三元组 ((i,j,val)),共 (nlog N) 个。询问 ((l,r)) 就是在这些 ((i,j,val)) 中找到满足 (lleq ileq jleq r) 的最小的 (val),按一个端点排序后双指针+区间数据结构即可。(O(nlog^2N))

    经典问题2

    给定一张图,支持:1、将与 (x) 距离为 (1) 的点权值 (+k); 2、查询 (x) 点权。

    简单地对度数进行根号分治,(x) 度数 (<sqrt{n}) 就暴力改,否则打上标记,询问时再加上。

    7.11

    CF数据结构杂题

    CF1446D2 Frequency Problem (Hard Version)

    基本结论:最优答案的两个区间众数中有一个是整个序列的众数。

    证明(反证):设整个序列的众数为 (x),选取的区间为 ([l,r]),若 (x) 不是 ([l,r]) 的众数,([l,r]) 的众数为 (y),那么可以把左右边界继续扩大,一定存在一个更大的 ([l',r']) 满足 (num_x=num_y),所以 (l,r) 不是最优解。

    方案Ⅰ:官方做法 (O(nsqrt{n}))。对于出现次数 (>sqrt{n})(y),至多有 (sqrt{n}) 种,把区间众数 (x) 出现的地方标记 (1)(y) 出现的地方为 (-1),问题就成了最长的权值为 (0) 的子串, (O(n)) 计算。若 (leq sqrt{n}),那么 (x) 至多也只有 (sqrt{n}) 个,枚举每种 (x) 的个数 (c),双指针找到每个 (num(x)=c) 的极大区间,顺便维护每种值出现次数,看一下是否存在 (y)

    方案Ⅱ:初始将所有 (x) 标记为无用,枚举 (y),对于每个 (y) 的位置,将其左、右最近的无用 (x) 标记为有用。答案区间 (num(y)=num(x)),被标记为无用的显然会多出,那么它们就是无用的。根据方案Ⅰ中对于出现次数 (leqsqrt{n}) 的做法——只有极大区间会成为答案,对应到这里,只有有用 (x) 左右 (1) 的位置可能成为答案,共 (num(b)) 个,计算答案可以做到 (O(num(b)))。那么由于 (sum num(y)leq n) ,统计答案 (O(n)),但要找每个 (y) 的前驱后继,用 (set) 简单维护 (O(nlog n)),可用序列线性并查集优化至 (O(n))

    CF679E Bear and Bad Powers of 42

    先考虑如果只有操作 (3) 怎么做。用线段树维护每个数与上面最近的 (42^k) 的差(区间最小值)。若最小的差值减完后为 (0),再执行一次; (>0),直接 (return)(<0),向下递归。因为每个数最多“不好” (log) 次,且最多向上 (log) 层,这么做的复杂度为 (O(qlog_2nlog_{42}V))(V) 为值域,即 (10^9n)

    再考虑操作 (2)。可以证明,直接在线段树上区间覆盖复杂度并不会升高。若不会用势函数分析,也可以采用下面所说的做法:

    需要一些骚操作:我们只将位置 (r) 修改为 (x),然后将 ([l,r-1]) 赋值为 (inf) 。用 (set) 维护非 (inf) 的位置(相当于维护连续段的状态)。修改区间 ([l,r]) 会导致连续段状态改变,要在 (l-1,r) 割一刀:把它们赋值为后面最近的非 (inf) 值,并修改 (set) 。查询只要在线段树上查后面第一个非 (inf) 的位置即可,复杂度依旧是 (O(qlog_2nlog_{42}V))

    如果能理解上述做法的核心思路,应该也能大致证明直接区间覆盖时间复杂度的正确性。

    CF700D Huffman Coding on Segment

    计算区间哈夫曼编码的长度只要知道每种数出现次数,可用莫队维护。然后进行 (sqrt{n}) 分治,出现次数 (>sqrt{n}) 的直接扔进堆里构建哈夫曼树,(<sqrt{n}) 的先统一合并为 (>sqrt{n}) 的节点后再加入。

    CF453E Little Pony and Lord Tirek

    如果一段区间内上一次被清空的时间参差不齐,就难以计算答案;和 (CF679E) 一样,存在区间覆盖操作,可以采用类似 (ODT) 的做法(这种玩意似乎叫“颜色段均摊”)。设位置 (i) 上一次被修改的时间为 (t_i),把 (t) 相同的一段极大区间 ([L_j,R_j]) 一起搞,用 (set) 维护这些区间。对于每次操作 ((l,r)),先在 (l,r) 的位置分割 ([L_j,R_j]),然后对于 ([l,r]) 中的所有 ([L_j,R_j]) 分别查询,最后将这些区间合并。初始时有 (n) 段,每次操作新割出 (O(1)) 段,在线段树上搞一次就减少一段,所以复杂度为 (O((n+m)log n))

    查询的具体方法:预处理出 (a_i) 表示 (i)(0) 开始多久能充满, ([l,r]) 的答案分为已经充满和未充满两个部分—— (a_i< t-t') 的求 ((t-t')sum r_i),剩下的求 (sum m_i),对 (a_i) 建立可持久化权值线段树即可解决。

    CF1515H Phoenix and Bits

    操作种类有点多,将它们逐个击破。

    基本步骤:可以在 (trie) 树上拆出 (log n) 棵子树 (p_1,p_2dots p_k) 组成值域 ([x,y]),下面的操作均在这些子树上进行。

    询问:和线段树一样,在 (trie) 树上记录子树权值和即可回答询问,(O(nlog V))

    (xor) 操作直接打上懒标记,用到的时候看看要不要 (swap) 并下传,(O(nlog V))

    (and,or) 操作就是强制修改某些位为 (0/1)。对 (trie) 子树内每个二进制位记录是否存在这一位为 (0/1) 的元素。若同时存在 (0,1),暴力把一边合并至另一边,共 (nlog V)(V) 为值域)个节点,合并一次少一个,合并一次至多需要向下跳 (log V) 层,复杂度 (O(nlog^2 V))。若只有 (0/1),等同于 (xor) 操作。空间优化:如果直接记录每个子树内每个二进制位是否存在,空间要开到 (nlog^2 V),其实我们只需要记录子树内的 (or/and) 和就可以判断了。

  • 相关阅读:
    Ubuntu分区挂载
    YOLOv3:Demo needs OpenCV for webcam images
    tf.strided_slice函数
    numpy:np.random.seed()
    python:split()函数
    python:set() 函数
    python:zip() 函数
    python:enumerate 函数
    电脑无法上网,DNS出现fec0:0:0:ffff::1%1问题
    python:map 函数
  • 原文地址:https://www.cnblogs.com/whx666/p/14994903.html
Copyright © 2011-2022 走看看