2024 暑假学习笔记
线性代数
向量#
我们定义向量是多维空间中一条带方向的线段,由于不太需要考虑其绝对位置关系,只考虑相对位置,一般都是平移到原点然后记录终点的坐标,记为
一般来说我们只探讨二维向量,因为是比较容易想的。
比如说:
我们可以称这个向量为
同时在空间中也有一些特殊的向量,比如:
,就是原点,由原点指向原点,初始的向量。 ,在 轴方向前进一步的向量。 ,在 轴方向前进一步的向量。 ,在更高维度上前进一步的向量。
我们称这些向量为基向量。
如图:
向量
下面介绍一下向量的加减法则:
。 。
如图:
如果是减法我们就加上其相反向量即可。
向量的数乘:
。
向量的点乘:
。
矩阵#
运算:
- 矩阵的加法:按位相加即可。
- 矩阵的数乘:按位相乘即可。
- 矩阵的点乘:
。 - 矩阵的转置:写为
, 。
基础矩阵:
- 单位矩阵:即主对角线上全为
,乘上任何矩阵都为它本身。 - 零矩阵:全为
的矩阵,乘上任何矩阵都为零矩阵。
很多 DP 转移可以写成矩阵形式,由于矩阵乘法是有结合律的,所以可以快速幂做。
唯一有用的可能是高斯消元,那东西必须得看代码。
行列式#
行列式仅针对方阵。
然后计算公式是:
一些性质:
- 交换两行,行列式值乘上
。 - 倍加,行列式值不变。
- 和的拆分(一行或一列),直接拆分相加即可。
根据性质,可以用高斯消元把下面全部消成
LGV 引理#
- Pro:给定一个具有特殊性质的平面图 DAG,求从初始点集到结束点集的不相交路径方案数(两个点集点的个数相同)。
特殊性质:将起始点集以及结束点集像二部图一样分开排列,将线拉直不相交(点集内部不连边,有的话就去除,反正无论如何也不会走,因为不相交)。
网格图是其中一种。
- 结论:定义
为路径 上边的权的乘积(初始全为 ), 表示 到 可能的路径之和, 为初始点集, 为结束点集,那么:
即这个矩阵的行列式。
- 证明:咕咕咕。
Matrix-Tree 定理#
-
Pro1:给你一张无向图,求其生成树个数。
-
结论:令
为度数矩阵(主对角线为度数,带权), 为邻接矩阵(可以有权), , 然后选择 中的一个格子,删掉它所在列和行,那么 。 -
证明:咕咕咕。
-
Pro2:给你一张有向图,求其外向/内向生成树个数。
-
结论,外向:
,内向: 。 -
证明:咕咕咕。
BEST 定理#
最好的定理!!!
-
Pro:给你一张有向图,要你求从
出发的欧拉回路条数。 -
结论:满足(
为以 为根的内向生成树个数):
- 证明:咕咕咕。
线性基#
基可以理解为基向量,线性基通常是关于异或的基。
然后就是做高斯消元,矩阵中剩下的数就是线性基所要的数了,然后表示一下即可。
然后我们可以用线性基求所有数的最大异或值和最小异或值,直接贪心即可。
如果要求第
拉格朗日插值#
即给出
其实通常我们取连续值然后用阶乘计算,那么计算单个就变成了
一般如果有式子那么就猜多项式系数,直接开到最大的做就完事了。。。
题目#
P2044 [NOI2012] 随机数生成器#
- Description:给定
和递推公式 ,求 , 。
首先这个题我们肯定要用矩阵快速幂,考虑构造矩阵:
然后初始矩阵为:
然后答案就是
P1962 斐波那契数列#
- Description:求斐波那契数列第
项, 。
直接上矩阵,不多说。
P2447 [SDOI2010] 外星千足虫#
- Description:给你若干个方程,形如一堆数的和
的值,让你解方程,并推算出最早可以解出的时刻。
其实我们不难发现可以转化成异或方程组,然后我们可以高斯消元解出来,也可以用线性基求解。
但是题目太毒瘤了,我们需要用 bitset 优化一下常数。
P2886 [USACO07NOV] Cow Relays G#
- Description:给你一张有向图,让你求从
到 恰好经过 条边的最短路。
我们定义广义矩阵乘法为 Floyd 的转移形式,然后我们把邻接矩阵对着这个东西求
P1397 [NOI2013] 矩阵游戏#
- Description:有些复杂,可以自己看原题。
其实就是每一行矩阵快速幂,然后换行的时候乘上另外一个矩阵,对
P3746 [六省联考 2017] 组合数问题#
- Description:让你求
。
其实我们组合意义一下,就是在
P6573 [BalticOI 2017] Toll#
- Description:有些复杂,可以自己看原题。
考虑到
考虑到矩阵乘法是有结合律的,所以我们多次询问采用线段树进行解决。
P3193 [HNOI2008] GT考试#
- Description:有些复杂,可以自己看原题。
考虑设状态
P7736 [NOI2021] 路径交点#
- Description:有些复杂,可以自己看原题。
我们发现本质就是 LGV 引理里的容斥,利用 LGV 引理做,完事。
P3317 [SDOI2014] 重建#
- Description:有些复杂,可以自己看原题。
我们发现 Matrix-Tree 定理本质上是要求生成树权乘积和的,那么我们考虑题目让我们求什么:
就是还要保证生成树以外的边不被选中。
然后共同除以
P4336 [SHOI2016] 黑暗前的幻想乡#
- Description:有些复杂,可以自己看原题。
我们发现容斥 + Matrix-Tree 定理即可。
就是
为什么要容斥呢?因为可能有多家公司用很多边,根据直觉,就应该这样容斥。
组合计数
很简单的一些公式:
- 递推公式:
- 吸收恒等式:
- 上指标求和(利用递推公式,加一项):
- 平行恒等式:
- 范德蒙德卷积:
- 二项式定理:
来看几个问题:
- 问题
:给你 个相同的球和 个不同的盒子,问有多少种办法把这 个球放进盒子?盒子不可以空。
利用插板法,不难得到
- 问题
:给你 个相同的球和 个不同的盒子,问有多少种办法把这 个球放进盒子?盒子可以空。
我们借
- 问题
:有不定方程 ,每个量都是非负整数,对于每个 , 有限制 ,计数这样的不定方程的解数。 。
考虑到容斥,所有的减去一个大于
然后是格路计数,就仿照卡特兰数,画两条线,然后上面那根对折以下就好了。
然后需要注意的是如果是有上下界,直接容斥即可,不要犹豫。
然后是斯特林数(由于不会打所以直接括号表示):
- 第一类斯特林数:
表示 个不同元素构成 个圆排列方案。递推:
- 第二类斯特林数:
表示 个不同元素构成 个非空子集。递推:
需要特别特别特别记住的公式#
利用这个公式我们可以推很多组合意义。
比如 Card 和自然数幂和(虽然不如直接拉插)。
题目#
P1450 [HAOI2008] 硬币购物#
- Description:给你
种硬币个数及面值,问恰好买 元物体有多少种方式?
考虑容斥,限制放的硬币种类,最开始再预处理一下多重背包就好了。
P1641 [SCOI2010] 生成字符串#
- Description:
个 和 个 ,要求前 位 的个数不能少于 的个数,问方案总数。
考虑组合意义,就是网格图画线,然后翻折一下求方案个数,然后稍微容斥一下即可。
CF1278F Cards#
- Description:有点复杂。
概率的题就是选中乘上选中的概率乘上没选中的乘上没选中的概率,推出来一下,然后用斯特林数幂展开,然后用二项式定理就做完了。
CF622F The Sum of the k-th Powers#
- Description:求
。
这个东西有个很明显的结论,就是它是
[AGC013E] Placing Squares#
- Description:有点复杂。
考虑分两类点,一类点是标记点,一类是没有标记点,然后我们设置两个不同的矩阵转移 DP,由于一共只会有线性个标记点,所以我们只需要做
CF1895F Fancy Arrays#
- Description:有点复杂。
考虑容斥,容斥下线后发现
[ABC281G] Farthest City#
- Description:有点复杂。
考虑我们将图分层做 DP,那么第
数据结构
并查集#
其实并查集的本质就是一颗树,只不过看你是只维护连通性还是维护其他信息。
种类并查集#
就按照种类分层,然后不同种类之间互相连边,通常能够描述不同种类之间的矛盾关系。
带权并查集#
就是树上每条边都带权,然后由于有边权,所以大部分情况下不能路径压缩,只能按秩合并。
笛卡尔树#
考虑这样一个对于区间
- 拎出最大值做根节点。
- 最大值两边的区间继续处理。
就是一个大根堆。
但是我们实际操作时不这么操作,因为复杂度是
我们这么处理:
for ( int i = 1; i <= n; i ++ ) {
int tmp = cnt;
while ( tmp && a[id[tmp]] > a[i] ) {
tmp --;
}
if ( tmp ) {
tree[id[tmp]].r = i;
}
if ( tmp < cnt ) {
tree[i].l = id[tmp + 1];
}
id[++ tmp] = i, cnt = tmp;
}
就每次试着往右边加,如果不行往上跳,复杂度
如果忘了也可以直接分治 + 线段树
哦,还有就是笛卡尔树的启发式搜索复杂度是对的(每次都往小的区间遍历)。
左偏树#
先建一个堆。
定义
然后合并堆的时候不断往下合并,如果左儿子的
为什么复杂度是对的,因为每一层都是满的(会交换),所以
放个详细点的合并代码:
int merge(int x, int y){
if(!x || !y){
return x + y;
}
if(a[y] < a[x]){
swap(x, y);
}
rs[x] = merge(rs[x], y);
if(dist[ls[x]] < dist[rs[x]]){
swap(ls[x], rs[x]);
}
dist[x] = dist[rs[x]] + 1;
return x;
}
可持久化#
可持久化的本质就是维护多个历史版本,将上一个版本需修改的节点原封不动的复制下载,继续更改。
然后可持久化数据结构复杂度必须是非均摊的,否则可以通过单一元素不同版本多次查询卡你(如 Splay)。
然后复杂度正确必须保证新增节点个数正确与结构正确。
可持久化字典树#
一般是 O/1 Trie。
就先可持久化以下。
然后我们会发现这个东西对于某些差分问题有可减性,即对应节点相减可以把重复部分消掉,然后我们可以进行最大/最小/第
放个插入代码,可能有点不一样:
void insert ( int &node, int k, int x ) { // k 表示位数
tree[++ cnt] = tree[node];
tree[cnt].siz ++;
node = cnt;
if ( k == -1 ) {
return ;
}
insert ( tree[node].ch[( x >> k ) & 1], k - 1, x );
}
上述代码如果是第
可持久化线段树#
一般也是更改节点可持久化,能用标记永久化就尽量用标记永久化,pushdown 在一些情况下复杂度是对的也可以做。
可持久化权值线段树也利用可减性做题。
这个没有固定代码,就是修改的时候新增结点就好了。
可持久化平衡树#
当分裂与合并的时候,复制一份,然后维护历史版本。
可持久化一般是 FHQ,但是这个东西的复杂度似乎不太对,但是很难卡,但是我知道可持久化 FHQ 的区间复制复杂度肯定是错的。
详细讲区间复制:把历史版本与当前版本合并就行。
来点合并分裂:
void split(int node, int val, int &r1, int &r2){
if(!node){
r1 = 0;
r2 = 0;
return ;
}
if(tree[node].val <= val){
r1 = newnode();
tree[r1] = tree[node];
split(tree[r1].rs, val, tree[r1].rs, r2);
update(r1);
}
else{
r2 = newnode();
tree[r2] = tree[node];
split(tree[r2].ls, val, r1, tree[r2].ls);
update(r2);
}
}
int merge(int x, int y){
if(!x || !y){
return x + y;
}
if(tree[x].key > tree[y].key){
int node = newnode();
tree[node] = tree[x];
tree[node].rs = merge(tree[node].rs, y);
update(node);
return node;
}
else{
int node = newnode();
tree[node] = tree[y];
tree[node].ls = merge(x, tree[node].ls);
update(node);
return node;
}
}
线段树 & 平衡树#
线段树#
主要是讲一些基本模型
考虑到线段树的基本结构,我们需要开
还有就是势能线段树,反正分析势能乱搞就对了(比如区间取
然后以下是基本总结:
线段树(Segment Tree)是一种基于树结构的数据结构,主要用于处理区间查询和区间更新操作。其基本原理如下:
数据结构:
- 线段树通常是一棵平衡二叉树。
- 每个节点代表数组中一段连续的区间,叶子节点对应数组中的单个元素。
构建过程:
- 线段树从根节点开始,递归地将数组划分为更小的子区间,直到每个叶子节点表示数组中的一个单独元素。
- 每个非叶子节点存储其子节点的合并结果(如区间和、最大值等)。
功能:
- 区间查询:线段树能快速求解任意区间的查询问题,如区间和、最小值、最大值等。
- 区间更新:支持快速更新数组中的元素,并且能够保持树结构的正确性。
操作复杂度:
- 构建:线段树的构建时间复杂度为
, 是数组长度。 - 查询:查询操作的时间复杂度为
。 - 更新:更新操作的时间复杂度为
。
应用场景:
- 适用于需要频繁进行区间查询和更新的场合,如动态统计区间内的最值、求和等问题。
- 在解决动态数组查询问题时,特别是针对静态区间的查询和更新操作时表现优异。
- 总结来说,线段树通过将数组元素组织成树结构,利用树的特性实现高效的区间查询和更新,是解决区间统计问题的一种经典数据结构。
平衡树#
一般都是写 FHQ Treap。
如果是 Splay 就是单旋双旋旋到顶,可以动态适配复杂度(如
如果是替罪羊树就是重构,这一类平衡树在乱搞方面有极大优势(如 KDT 重构),但是基本不太好写,复杂度也有问题,但是好想。
然后附上分裂合并代码:
void split ( int node, int &r1, int &r2, int k ) {
if ( !node ) {
r1 = r2 = 0;
return ;
}
if ( tree[node].val <= k ) { // 如果已经小于,整个左子树也是满足的,分裂右子树
r1 = node;
split ( tree[node].rs, tree[node].rs, r2, k );
}
else { //不满足,右子树肯定满足 > k,合并到 r2,分裂左子树
r2 = node;
split ( tree[node].ls, r1, tree[node].ls, k );
}
pushup ( node );
}
int merge ( int x, int y ) { // 需保证 x 子树值比 y 子数值小
if ( !x || !y ) {
return x + y;
}
if ( tree[x].key < tree[y].key ) { // 随机合并,期望 log
tree[x].rs = merge ( tree[x].rs, y );
pushup ( x );
return x;
}
else {
tree[y].ls = merge ( x, tree[y].ls );
pushup ( y );
return y;
}
}
题目#
P3261 [JLOI2015] 城池攻占#
直接每个点维护左偏树,然后暴力往上合并即可,由于一个元素最多删除一次,所以复杂度有保证。
P1552 [APIO2012] 派遣#
考虑到从下往上合并。
考虑一个人如果已经不能被选了,那么越往上走就越不能选,所以一个元素只会被丢一次,复杂度是对的。
P4755 Beautiful Pair#
考虑笛卡尔树启发式合并,小的区间暴力,大的区间直接加入树状数组查询即可,注意要好好分析启发式合并的复杂度,否则会挂。
P3899 [湖南集训] 更为厉害#
典题。
直接线段树合并统计贡献即可(发现深度是一段区间)。
P4592 [TJOI2018] 异或#
对链和子树分别做可持久化 0/1 Trie 上的二分,然后就没了。
P3586 [POI2015] LOG#
直接上平衡树统计一下贡献,判断一下贪心选择能否成功。
P4514 上帝造题的七分钟#
考虑到空间限制不允许我们使用二维线段树,所以我们可以二维树状数组维护差分数组的四个项,然后二位前缀和一下。
数论
exgcd#
通常来说,我们用辗转相除法求
根据裴属定理,
然后我们考虑在辗转相除的过程中求,具体来说:
- 辗转相除已经解出
- 我们要求
的解。 - 将上面化简得:
。 - 化简:
。 - 然后继续递推就好了
Lucas 定理#
就是拆成
具体来说:
通常运用在模数较小,然后求逆元会挂得情况下(如果你看不懂,你得逆元得重新学)。
CRT#
没用,不讲。
exCRT#
就我们设两个东西公共解为
乘法逆元#
若
使用场景:
- 若出现
,不能等价于 ,此时可以用 乘以 的逆元 , 。
求解逆元:
- 求单个整数的逆元:
-
- 扩展欧几里得算法:
- 由此可知,乘法逆元不一定存在,因为方程并不一定有解。
- 乘法逆元若存在,那么有无数个,但在模
意义下只有一个(最小解)。
-
- 有限情况下使用费马小定理:
费马小定理:若
,且 ,那么
,所以 为 在模 意义下的逆元。
- 求
的所有整数的逆元(前提是存在):
,设 ,其中 ,则 , ,那么
- 注意前提要存在。
- 线性求逆元:
- 先预处理阶乘,然后处理出
的逆元,每次都乘上 就可以线性求解 的阶乘的逆元了,这个用的一般比较多,但是大部分时候用 就够了。
数论
基本数论#
数论函数:定义域为正整数的函数。
欧拉函数:定义
特殊点:
。
特殊性质:
- 如果
, ,证明:
与
不互质的 一共有 个(含有 ),所以 ,证毕。
- 若
,则 ,证明:
分成
若干个区间,我们设 里有 与 互质,那么 仍然与 互质,且只存在这些在 之间的数与 互质,因为若有一个另外的数 ,那么 理应与 也互质,推翻假设,证毕。
- 若
, ,证明:
欧拉函数是积性函数。
欧拉函数的求法:
- 用定义法求单个
的欧拉函数 ,公式如下:
。
- 同线性筛求
以内所有数值的欧拉函数,具体操作:
void init () {
phi[1] = 1;
for ( int i = 2; i <= 1000000; i ++ ) {
if ( !vis[i] ) {
prime[++ cnt] = i;
phi[i] = i - 1;
}
for ( int j = 1; j <= cnt && i * prime[j] <= 1000000; j ++ ) {
vis[i * prime[j]] = true;
if ( !( i % prime[j] ) ) {
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
phi[i * prime[j]] = phi[i] * ( prime[j] - 1 );
}
}
}
莫比乌斯反演#
就数论函数典型套路,然后讲莫比乌斯反演。
就是
然后记住一些数论函数间的乘积关系,不过大部分没用。
然后一些典型推柿子:
- 转换变量,枚举最大公约数。
- 然后进行区域划分,分块套分块。
- 整理式子,改变顺序。
- 充分理解。
没有什么别的,就是推式子。
然后放一些经典例题(其实杜教筛学没学都一样,反正不会考)。
题目#
整除分块#
记录一下:
主要就是利用除法的连续性。
P3327 [SDOI2015]约数个数和#
又是一道清新的莫比乌斯反演(真是令人作呕)。
Tm 的想了
有了这个性质就秒秒钟切,先令
根据直觉可得:
很显然
然后发现
P4213 【模板】杜教筛(Sum)#
包含着很多悲伤的故事(指卡 long long)。
考虑对于任意积性函数
正常做法是
首先我们需要知道狄利克雷卷积是什么:
我们定义
其实这个东西可以化成卷积的形式:
只不过上面那种形式比较通用。
考虑杜教筛的原理,先构造
然后:
然后我们发现
其复杂度为 其实没啥用。
P5221 Product#
感觉很难欸。
首先根据我们的瞪眼法不难发现
然后你发现
先看分子:
解释一下这里为什么,你考虑当
然后再看分母(公式警告):
这里很有道理吧!
然后我们只看指数:
我们发现,对于每个
那么我们便要求:
然后你提交一发,发现 long long 会炸!所以我们需要用到欧拉定理:
当
然后就是疯狂注意细节。
作者:alexande
出处:https://www.cnblogs.com/alexande/p/18294174
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】