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 , min{sx , 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=na(a=0.5),但对于一些特殊的题目,可以通过改变a的大小,改为 p = a ∗ n a p=a*n^a p=a∗na来优化常数,
树分块
线性的分块已经掌握,考虑如何在树上进行分块,有一下几种方式:
- DFS序分块,保证块的大小和个数,但不保证直径和连通性。
- 深度分块,bfs按照深度进行分块,保证直径和连通性,不保证大小和个数。
- 大小分块,对树进行dfs,若当前节点的父节点所在块的大小 < n <\sqrt n <n,则将该节点加入父亲块。否则新建一个块。除个数外都保证
莫队
一种离线算法,基本思想时对于m组询问,先全部记录下来,将左节点为第一关键字,右节点为第二关键字进行排序,暴力处理一组询问,对于后面的询问只需移动左右节点并处理即可,另外还有回滚莫队等扩展。
Day.9
主要讲了字符串的一些操作和算法
字符串Hash
Hash具体是指通过建立Hash函数,在某一节点的位置和关键字建立某种联系,例如在a数组中插入元素b,则存储在 a [ H a s h ( b ) ] a[Hash(b)] a[Hash(b)]中。
如何建立合适的Hash函数你呢,有以下几种方式:
- 除余法,找到合适的素数mod,对于Hash(b)=b%mod。
- 多项式法,对于一个序列 x 1 , x 2 . . . x n x_1,x_2...x_n x1,x2...xn, H a s h ( x 0 , x 1 . . . x n ) = x 0 + x 1 ∗ a 1 . . . + x n ∗ a n Hash(x_0,x_1...x_n)=x_0+x_1*a_1...+x_n*a_n Hash(x0,x1...xn)=x0+x1∗a1...+xn∗an
当然,Hash函数通常并不能保证不出现冲突,这是我们就需要
闲散列表,具体就是不严格按照Hash函数来选择位置,在发生冲突时按某种方法选择另一个位置。
- 线性试探,发生冲突时,向两边试探相邻位置。
- 平方试探,向右平移 1 2 , 2 2 . . . 1^2,2^2... 12,22...位置。
- 再散列,将Hash值再次进行Hash。
除闲散列表外还有开散列表。
在每个位置维护一个列表,在发生冲突时,存储Hash值一样的值。
trie,kmp和ac自动机
kmp主要用于单串模式匹配,trie树和ac自动机主要用于多串存储和前缀匹配。
可持久化trie树
思想类似于和持久化线段树,在更改节点时新建一条从根节点到更改节点的路径,其他节点指向上一棵trie树的节点。
序列自动机
给定若干个串,询问他们是否时字符串S的子串,
从后向前对于字符串S的每一个位置i,对于字符集中的每一个字符a,尝试找到距离i最近的a的位置。
Manacher(马拉车)
manacher算法可以在O(n)的复杂度内求出一任一字符为中心的最长的回文子串。
先对字符串进行预处理,在每个字符之间插入一个不常用的字符,在开头和末尾再插入一个不同的字符。经过这样的处理后,就无须判断回文串的奇偶性。
我们设p为当前字符所在的位置,rr为右边最长回文子串的右端点,c为右边最长回文串中心的位置。数组r[i]表示以第i个字符为中心的最长回文子串的长度,然后进行分类讨论。
当p>rr时,r[p]=1。
当p<=rr时,我们找到p点关于c的对称位置d,d=2*c-p。
当p+r[d]<r时,r[p]=r[d]。
当p+r[d]>=rr时,r[p]=rr-p+1。
void manacher()
{
int c=1,rr=1;
r[1]=1;
for(int p=1;p<=n;p++)
{
r[p]=min(rr-p+1,r[c*2-p]);
while(s[p-r[p]]==s[p+r[p]])
{
r[p]++;
}
if(rr<r[p]-1) rr=p+r[p]-1,c=p;
}
}
Day.10
依据二分的基础讲了关于分治思想的算法。
cdq分治
对于操作中的每个查询操作,其结果为“修改操作”对于“初始数据”的影响。
设共有m项操作,定义 w o r k ( l , r ) work(l,r) work(l,r)为,对于l到r中的任一个数k,其中 1 < = l < = r < = m 1<=l<=r<=m 1<=l<=r<=m,若第k项操作为查询,则计算第 l l l ~ k − 1 k-1 k−1项中修改操作对此次查询的影响。
设 m i d = ( l + r ) / 2 mid=(l+r)/2 mid=(l+r)/2,递归计算 w o r k ( l , m i d ) work(l,mid) work(l,mid)和 w o r k ( m i d + 1 , r ) work(mid+1,r) work(mid+1,r),最后计算 l l l ~ m i d mid mid操作中修改对 m i d + 1 mid+1 mid+1 ~ r r r中查询的影响。
像是这样基于时间进行分治的算法,被称为CDQ分治,整个分治的过程时间复杂度为 O ( l o g m ) O(log\ m) O(log m),常出现在二维偏序,三维偏序等题目。
整体二分
整体二分不同于CDQ分治在时间顺序上进行分治,而是对于所有询问操作的值域进行二分。接下来结合一道例题具体进行分析。
例题:给定一个长度为n的空集,以及m次操作,操作有两种
- 将 [ l , r ] [l,r] [l,r]中的集合插入数字c。
- 查询 [ l , r ] [l,r] [l,r]中所有数字的第k大值。
分析:考虑若我们已知第 [ l , r ] [l,r] [l,r]项操作中答案的值域为 [ L , R ] [L,R] [L,R],那么我们就可以依次执行这些操作,对于查询操作,只需判断小于等于 M I D MID MID的数的个数与k的大小关系,也就是判断其答案的值在 [ L , M I D ] [L,MID] [L,MID]中还是在 [ M I D + 1 , R ] [MID+1,R] [MID+1,R]中,对于修改操作,忽略 c > M I D c>MID c>MID的操作,经过这样的处理后,就能够依照值域将答案分为两部分,一组答案小于等于 M I D MID MID,另一组大于等于 M I D MID MID。此时进行分类讨论。
对于答案小于等于 M I D MID MID的那一组, c > M I D c>MID c>MID的修改对它们不造成影响,只需要让它们再和所有 c < = M I D c<=MID c<=MID的修改进行以上操作。
对于答案大于等于 M I D MID MID的一组,将查询的k减去小于等于 M I D MID MID的数的数量,再让它们和 c > M I D c>MID c>MID的修改进行以上操作。
对于每层的处理复杂度为 O ( n l o g n ) O(n\ log\ n) O(n log n),一共有 l o g n log\ n log n层,所以整体复杂度为 O ( n l o g 2 n ) O(n\ log^2\ n) O(n log2 n)。
点分治
对于一棵大小为n的树,以其重心为根,则重心的子树大小都不超过 2 n \frac{2}{n} n2个,我们对其子树也进行相同的操作,那么我们可以将一个问题先对整棵树讨论,再对重心的子树讨论,再对重心子树的子树讨论,不断向下进行,直到叶节点。整体复杂度为 O ( l o g n ) O(log\ n) O(log n)。
点分树(动态点分治)
依据点分治的思想,将整棵树的重心作为父节点,重心子树中的重心作为其子节点,以此类推,最后可得到一棵深度为 l o g n log\ n log n的树,该树满足以下性质:
- 对于任意两节点 u , v u,v u,v,他们的LCA都在 u , v u,v u,v的路径上。
- 对于任一点子树中的节点都在其分治的子树中。
Day.11
数论
拓展lucas定理
bsgs
exbsgs
欧拉函数及相关推论
欧拉定理
拓展欧拉定理
miller-rain算法
pollard s rho 算法
pollard`s rho 算法加速
阶与原根及性质
求原根
狄利克雷卷积
莫比乌斯函数及其相关卷积结论
莫比乌斯反演
杜教筛
求狄利克雷卷积
Day.12
二项式定理
( a + b ) n = ∑ i = 0 n ( n i ) a i b n − i , n ∈ N (a+b)^n=\sum_{i=0}^{n} \left(\begin{array}{c} n \\ i\end{array} \right)a^ib^{n-i},n\in N (a+b)n=∑i=0n(ni)aibn−i,n∈N
其中n可以扩展到任意实数a,变为 ( a + b ) a = ∑ i = 0 ∞ ( α i ) a i b α − i , α ∈ R (a+b)^a=\sum_{i=0}^{\infty} \left(\begin{array}{c} \alpha \\ i\end{array} \right)a^ib^{\alpha-i},\alpha\in R (a+b)a=∑i=0∞(αi)aibα−i,α∈R
组合数常用结论:
- 递推式 ( n m ) = ( n − 1 m − 1 ) + ( n − 1 m ) \left(\begin{array}{c} n \\ m\end{array} \right)=\left(\begin{array}{c} n-1 \\ m-1\end{array} \right)+\left(\begin{array}{c} n-1 \\ m\end{array} \right) (nm)=(n−1m−1)+(n−1m)
- 翻转上指标 ( n m ) = ( − 1 ) m ( m − n − 1 m ) \left(\begin{array}{c} n \\ m\end{array} \right)=(-1)^m\left(\begin{array}{c} m-n-1 \\ m\end{array} \right) (nm)=(−1)m(m−n−1m)
- 上指标求和 ( n + 1 m + 1 ) = ∑ i = 0 n ( i m ) \left(\begin{array}{c} n+1 \\ m+1\end{array} \right)=\sum_{i=0}^{n}\left(\begin{array}{c} i \\ m\end{array} \right) (n+1m+1)=∑i=0n(im)
- 下指标求和 ( n + m k ) = ∑ i = 0 k ( n i ) ( m k − i ) \left(\begin{array}{c} n+m \\ k\end{array} \right)=\sum_{i=0}^{k}\left(\begin{array}{c} n \\ i\end{array} \right)\left(\begin{array}{c} m \\ k-i\end{array} \right) (n+mk)=∑i=0k(ni)(mk−i)
- 上指标卷积 ( n + 1 a + b + 1 ) = ∑ i = 0 n ( i a ) ( n − i b ) \left(\begin{array}{c} n+1 \\ a+b+1\end{array} \right)=\sum_{i=0}^{n}\left(\begin{array}{c} i \\ a\end{array} \right)\left(\begin{array}{c} n-i \\ b\end{array} \right) (n+1a+b+1)=∑i=0n(ia)(n−ib)
二项式反演
f n = ∑ i = 0 n ( n i ) g i f_n=\sum_{i=0}^{n}\left(\begin{array}{c} n \\ i\end{array} \right)g_i fn=∑i=0n(ni)gi
⇓ \Downarrow ⇓
g n = ∑ i = 0 n ( n i ) f i ( − 1 ) n − i g_n=\sum_{i=0}^{n}\left(\begin{array}{c} n \\ i\end{array} \right)f_i(-1)^{n-i} gn=∑i=0n(ni)fi(−1)n−i
二项式反演本质上是容斥的另一种数学形式,可以完成从“至多”到“恰好”的转化。它还有另一种形式:
f
k
=
∑
i
=
k
n
(
i
k
)
g
i
f_k=\sum_{i=k}^{n}\left(\begin{array}{c} i \\ k\end{array} \right)g_i
fk=∑i=kn(ik)gi
⇓
\Downarrow
⇓
g
k
=
∑
i
=
k
n
(
i
k
)
f
i
(
−
1
)
k
−
i
g_k=\sum_{i=k}^{n}\left(\begin{array}{c} i \\ k\end{array} \right)f_i(-1)^{k-i}
gk=∑i=kn(ik)fi(−1)k−i
第二类斯特林数
感受
今天的组合这一块并未完全吸收,还需在以后反复复习,多加练习。
Day.13
上午讲了概率与期望,下午又讲了博弈论。
概率与期望
期望dp的正推与逆推 ,期望与方案数之间的转化, 无法dp的期望问题, 有后效性的期望dp
高斯消元
分块消元和稀疏矩阵消元
博弈论
Nim游戏 SG函数 有向图游戏
Day.14
多项式运算
FFT
深搜式 迭代式
Purfer序列
说明
以上空着的都是博主在集训时并未掌握的,先只将知识点罗列出来,再经过一段时间的复习,就立马来填坑.