2021 7.5~7.20日集训总结
Posted zero_orez6
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2021 7.5~7.20日集训总结相关的知识,希望对你有一定的参考价值。
Day.1
集训开始的第一天,以数据结构为主体,在之前的基础上进行了不小的扩展。
并查集
启发式合并:一种根据子树大小为基准的合并集合的方式,能够使得“合并集合”操作和“询问”的复杂度降到 O ( l o g n ) O(log n) O(logn)
路径压缩:在只考虑每个点与其根节点的情况下,我们可以将任意一个子树的父亲设置为根节点,使得查询操作降到 O ( 1 ) O(1) O(1)
最小生成树
简单的过了一下基础的kruskal和prim算法,然后就开始讲一些之前闻所未闻的算法
kruskal重构树:在kruskal合并的过程中,将合并的过程新建一个节点,表示两个并查集的“父亲”节点,通过这样的操作,我们就可以用这个节点代表经过不超过某个权值的边的联通块。经过这样的特殊构造就可以满足某些题目的要求。
例如:当题目要求从节点1~v不经过权值超过a的最短路时,经过kruskal重购树的构造后,从节点v向上走的过程中,对应的权值会逐渐变大,也就是说一定会有一个节点其对应的子树是从这个节点出发经过不大于 a 的边能到达的所有点,我们只需要取这里面最短路的最小值即可
树状数组
支持区间查询,单点修改,利用差分进行处理可支持单点查询和区间修改,以及扩展运用:二维树状数组。
ST表
基于倍增思想,对于序列a,构造ST表s[i][j]表 i i i~ i + 2 j i+2^j i+2j这一段的最大值,那么可以得到递推式 s [ i ] [ j ] = m a x ( s [ i ] [ j − 1 ] , s [ i + 2 j − 1 ] [ j − 1 ] ) s[i][j]=max(s[i][j-1],s[i+2^j-1][j-1]) s[i][j]=max(s[i][j−1],s[i+2j−1][j−1]),在 O ( n l o g n ) O(n\\ log n) O(n logn)的时间内构造出ST表,查询区间最大值
O(1)计算LCA:用欧拉序将树上问题转化为序列问题,此时对于LCA的查询就变为两个节点欧拉序区间内深度最低的点,也就可以用ST表来维护了。
线段树
ZKW线段树普通的线段树是从根节点从上向下操作,而ZKW线段树可以自下而上从叶节点开始,其常数更低,但不够灵活
动态开点:通常线段树4倍的空间较大,动态开店则是在根据访问去分裂区间。
历史最值问题
对于线段树上一个节点,需要分别维护其具体的信息和其上的标记,我们以节点当前的时间节点来看,我们必然维护有这个
时间之前的历史最小值,但是没有维护的是当前时间点到标记更新这段时间的历史最小值。
为了解决这个问题,我们可以在节点 x 上维护 (mx , hx ),其中 mx 为当前这个节点的最小值,hx 为历史最小值。同时,维护标记 (dx ,sx ),其中 dx 表示所有作用在这个区间上的 ∆ 的总和,sx 表示作用在这个区间上的所有 ∆ 的和的历史最小值。考虑节点 x 的标记 (dx ,sx ) 传递到 y 上,那么我们有
( d y , s y ) → ( d x + d y , m i n s x , d x + s y ) (dy ,sy ) → (dx + dy , minsx , dx + sy)% (dy,sy)→(dx+dy,minsx,dx+sy)
(并未完全看懂)
树链剖分
根据子树大小对于每个节点进行分类,一个节点子树大小最大的那个儿子为重儿子,一个节点与其重儿子的连边为重边,相互之间连接的重边为重链。
进行处理完之后,对于任意一个节点,其向根的路径中最多只会经过 l o g n log\\ n log n条重边,每经过一条轻边,子树大小翻倍,理由如下:
考虑对于任意一个节点 y y y,其父节点为 x x x且 y y y为 x x x的轻儿子。此时节点 y y y一定存在一个子树大小至少一样的兄弟节点,也就是说 s i z e x > = s i z e y ∗ 2 size_x>=size_y*2 sizex>=sizey∗2
这时,我们在构造dfs序时优先访问每个节点的重儿子,就可以满足子树和重链在dfs序列上形成连续的区间
标记永久化
就是对于懒标记不再下传,在进行询问时直接将标记下传到答案上
李超线段树(又一个神奇的东西
简洁的来说,就是在线段树上的每个节点上保存一条线段,统计这个节点所对应区间的直线上的最大值。
每次添加直线时判断这条直线是否对答案有贡献,若有,则进行替换,否则,直接不做修改。
吉司机线段树
与李超线段树思想一致,用于处理类似于“将一个区间所有大于x的数变为x”的操作,记录每个区间的最大值,次大值以及次大值的出现次数,对于每个修改操作进行分类讨论
- 若最大值小于x,不进行任何操作
- 若最大值大于x,次大值小于x,则直接更改最大值
- 若都不满足,则继续向下递归。
感受
第一天还是蛮自闭的,第一次集训开始学会去适应那种学习方式。
Day.2
第二天基于第一天数据结构进行扩展,讲述了可持久化数据结构。
数据结构的可持久化就是将一个数据结构的历史状态全部存储下来,以便快速查找之前出现过的某个操作的结果
主席树(可持久化线段树)
大致思路是每当修改一个节点时,新建一条从根节点到此节点的路径,其他子节点连向上一棵线段树的子节点
线段树合并:直接暴力合并,其复杂度取决于叶节点的总数量,为 O ( m l o g n ) O(m\\ log \\ n) O(m log n)
可持久化数组
基于时间建立数组,在新增元素时开辟新的节点即可。
可持久化并查集
可持久化并查集基于可持久化数组,注意由于路径压缩会导致多次修改从而新建多个节点占用大量空间,所以可持久化并查集多使用启发式合并来实现。
感受
由于以前并未接触过可持久化数据结构,所以对于可持久化数据结构的理解一知半解,还是需要通过代码来具体理解其本质
Day.3
从今天开始的两天都是关于dp的内容,今天主要是两大类dp——树形dp和数位dp
树形dp
顾名思义,树形dp是在树上进行dp,由于树层层递进和没有环的特殊性质,所以树形dp通常用递归来解决。
经典例题:选课:有若干门课,每门课有其学分,每门课有一门先修课或零门先修课,有先修课的必须先学习先修课再学习该课程。
根据题目中“先修课”这一特殊要求,可以看出所有课之间的关系呈树状结构,所有课共同组成一个森林,进行树形dp。
数位dp
一般是问你一个区间内有多少数满足要求,数据范围可能过大,需要依据题目要求对题目中的数拆开成每一位进行dp
Day.4
状压dp
状态压缩dp,是利用二进制的各种性质来表示状态的一种dp方式,状压实际上是枚举每一位的状态,比较暴力,复杂度为 O ( 2 n ) O(2^n) O(2n),但一些题目能够依照题意排除不合法的情况,使枚举的方案数大大减少
dp优化
老师在下午主要结合一些题目讲了优化dp(也有些非dp题目)的几种主要方式
单调队列优化
单调队列优化的本质是通过单调队列的单调性来保证dp时的最优性,及时排除不合法或较劣的决策
单调队列最多被运用在优化多重背包:
复杂度为 O ( n m l o g k ) O(n \\ m\\ log\\ k) O(n m log k)的多重背包dp式为 d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j − k ∗ v [ i ] ] + k ∗ w [ i ] , d p [ i ] [ j ] ) dp[i][j]=max(dp[i-1][j-k*v[i]]+k*w[i],dp[i][j]) dp[i][j]=max(dp[i−1][j−k∗v[i]]+k∗w[i],dp[i][j]),其中 0 < = k < = c [ i ] 0<=k<=c[i] 0<=k<=c[i]。仔细观察可发现对于不同的v[i],每次状态转移只会发生在同一组中,所以上面的dp式可转化为 d p [ i ] [ b + x ∗ v [ i ] ] = m a x ( d p [ i − 1 ] [ b + ( x − k ) ∗ v [ i ] ] + k ∗ w [ i ] , d p [ i ] [ b + x ∗ v [ i ] ] ) dp[i][b+x*v[i]]=max(dp[i-1][b+(x-k)*v[i]]+k*w[i],dp[i][b+x*v[i]]) dp[i][b+x∗v[i]]=max(dp[i−1][b+(x−k)∗v[i]]+k∗w[i],dp[i][b+x∗v[i]]),经过这样的转化后,对于不某一个 ( x , b ) (x,b) (x,b)对应一个j,若我们确定b,那么max中就变成了一个与x无关的式子,可以用单调队列来进行优化。这时复杂度就变为了 O ( n m ) O(nm) O(nm)
矩阵乘法优化
通过构建矩阵来相互乘或加来减少dp次数,从而减少时间复杂度。通常需要结合快速幂等。
斜率优化
将一些已推出的dp式转化为 d p [ j ] = k ∗ s u m [ i ] + b dp[j]=k*sum[i]+b dp[j]=k∗sum[i]+b的形式,在这个式子,利用dp式中的变量作为x轴,y轴,对于不同的决策 ( i , j ) (i,j) (i,j)都会对应平面直角坐标系中的一个点,而对于不同的i或j,其斜率也不同,对于每个待求解的状态dp[j],都会对应一个一定的斜率,和若干个不同的截距(一个j对应若干个的i),我们要做的,就是按照题目找到对于当前斜率,最符合题意的截距,从而进行dp。
此外,还可以通过题目的要求,在平面直角坐标系上维护一个凸包,从而减少点的数量,进而减少判断次数。
Day.5
Day.5,Day.6又开始了图论专题
图论基础
前一部分主要将了图的存储,拓扑序,欧拉回路,树,最短路,生成树以及最小生成树。
这里重点说一下欧拉回路,之前暑假集训只是接触过并不熟练,这次老师的又一次讲解,加上练习题令我印象深刻。
以下为老师PPT中欧拉回路的性质
若想要判断一张图是否为欧拉路径或回路
- 先根据上面的性质判断每一个点的出度及入度。
- 从起点开始dfs,欧拉回路中任意一个节点均为起点。
- dfs退栈时记录答案。
- 遍历完所有边后,倒序输出。
LCA的几种不同求法
ST表
上面有提到过,通过预处理可以做到 O ( 1 ) O(1) O(1)求LCA。
倍增
设f[x][k]为从x节点向上走 2 k 2^k 2k步所到达的节点,若该节点不存在,则令f[x][k]=0,特别的,f[x][0]为x节点的父节点,当k在 1 1 1~ l o g n log\\ n log n时, f [ x ] [ k ] = f [ f [ x ] [ k − 1 ] , k − 1 ] f[x][k]=f[f[x][k-1],k-1] f[x][k]=f[f[x][k−1],k−1].
基于f数组,我们便可以根据以下步骤求出任意两节点x,y的LCA
- 设x的深度大于y的深度。
- 从x节点依次尝试向上走 2 l o g n . . . 2 1 , 2 0 2^log\\ n...2^1,2^0 2log n...21,20步,若当前到达的位置比y节点深,则令x=f[x][k]。
- 若此时x==y,说明已找到LCA,即y点本身。否则将x,y节点同时向上移动且保证两点不相交。
- 此时x,y点的LCA即x节点的父节点。
对于多次查询,其复杂度为 O ( ( n + m ) l o g n ) O((n+m)log\\ n) O((n+m)log n)
向上标记法
从一个节点向根节点走,并标记经过的节点。再从另一节点向根节点走,第一次遇到的已标记的点便是两点的LCA,最坏情况下复杂度为 O ( n ) O(n) O(n)
Tarjan优化向上标记
Day.6
连通分量
连通分量分为有向图和无向图,在有向图中有强连通分量,若一个有向图中任意两点互相可到达,则称该有向图为强联通。无向图有双连通分量,双连通分量分为边双连通分量(割边)和点双连通分量(割点),若一张无向图中不存在割边的话,则称这张图为边双连通分量,点双连通分量以此类推
Tarjan
在讲课时老师由浅入深在Tarjan方面讲了许多,将再写一篇总结进行详细讲解。
Day.8
主要是分块和莫队
分块
在之前就已经接触过并写了总结,在这里主要将老师讲的之前未接触的列出来。
块状数组或块状列表时数组和链表相结合的产物,也就是用一个以数组为元素的链表来存储数据,此时插入和删除操作复杂度取决于数组的大小p,而查询操作则取决于整体链表大小q,对于随机数据,要想令复杂度平摊,则显然有 p = q = n p=q=\\sqrt n p=q=n,这就是分块思想的由来,当不需要插入和删除操作时,可以依据这个思想将上述的链表换为数组,也就是数组套数组。当然外层的数组也可以套其他东西,例如线段树,树状数组等。
通常情况下设块大小为 p = n p=\\sqrt n p=n,也就是 p = n a ( a = 0.5 ) p=n^a(a=0.5) p=7.6~7.20集训总结