生成随机数(未完工)

参考 引用头文件(常用) #include <stdlib.h> //生成随机数、设定随机数种子 #include <ctime> //生成随机数种子(1) #include <time.h> //生成随机数种子(2) #include <unistd.h> //等待(sleep(...)或usleep(...)),该头文件现在不常用 随机种子 srand(time(0) ^ clock()); 注:随机种子为time(0) ^ clock(),这个随机种子可以再毫秒级别内生成(大概率)不同的随机种子。 取 $[l,r]$ 内的随机数 #define random(l, r) (rand() % ((LL)(r) - (LL)(l) + 1) + (LL)(l)) 注:rand函数只能生成 $[1,32767]$ 内的随机数(见宏RAND_MAX),大概只能生成位数 $\leq 5$ 的随机数;测试程序: LL ma = 0; for(LL i = 1;i <= 200;i++){ srand(time(0) ^ clock()); LL t = rand(); cout << t <<endl; ma = max(ma, t); usleep(10000); } cout << ma <<endl; 所以此处引用一个新方法:mt19937(2022/12/24:现在开始使用mt19937_64了,它可以生成 $[1,2^{64})$ 内的随机数,大一倍)。 其实它的语法和rand的语法差不多 (才怪),是这样的: 引用头文件(常用) #include <random> //生成随机数、设定随机数种子 #include <ctime> //生成随机数种子(1) #include <time.h> //生成随机数种子(2) #include <unistd.h> //等待(sleep(...)或usleep(...)),该头文件现在不常用 随机种子 mt19937 _rand(time(0) ^ clock()); 注:这个随机种子也可以再毫秒级别内生成(大概率)不同的随机种子 取 $[l,r]$ 内的随机数 (就差一个字符_而已) #define random(l, r) (_rand() % ((LL)(r) - (LL)(l) + 1) + (LL)(l)) 据测试,这个函数可以生成在unsigned int范围内的随机数(即可以生成 $[1,2^{32})$ 内的随机数),测试程序: ...

2025/02/09 15:28:00

树链剖分

第一次看到树链剖分(简称树剖)可能觉得它非常的深奥,但其实树剖的原理非常简单,下面先说树剖大概的实现思路和解决题型。 概览 其实树剖就是把一棵树分成若干条链,使得每个点都出现在了刚好一条链上。 并且,我们称在某条链上的边为“重(zhòng)边”,不在任何一条链上的边为“轻边”。 然后,我们按某种规则去DFS,求出新的DFS序,然后解决对树上路径询问的问题。 而且,通过合理的设计每条边的轻重,可以把复杂度控制在 $O(\log n)$ 级别。 解决题型 上面也提到了,树剖就是用来解决对树上路径进行询问的问题的算法,当然也可以解决对子树询问的问题,不过属于大材小用了。 此外,树剖属于工具类算法,所以往往会配合树状数组、线段树、分块等算法出现。 我把算法分为了两类,“材料类算法”和“工具类算法”,其中: 材料类算法可以单独考,比如: 数论 推公式 DP 树状数组 线段树 … 工具类算法里面必须要套材料类算法才能考出来,比如: 二分 01分数规划(二分的一个分支) CDQ分治(需要配合树状数组) 整体二分(需要配合树状数组) 树剖(这里讲的) … 往往一些较难的题目,都是要用较难的材料类算法(比如DP),或者工具类算法配合材料类算法才能解决的题目。 而对于后者,难度会更大一些,因为后者的关键在于转化后使用工具类算法解决。 算法实现 下面说一下树剖的实现。 两遍DFS 我们考虑DFS,当递归到点 $x$ 的时候,找到 $x$ 的儿子中,子树大小最大的那个儿子 $to$。 然后,我们把 $x \to to$ 这条边设为重边,其他边设为轻边。 得到每条边的轻重后,我们再做一遍DFS。 而这一遍DFS是为了找到DFS序。 而且,DFS序有一个要求,我们要先遍历有重边连接的儿子。 这样的话,对于任意一条树上的链,链上所有点的DFS序连续,而且是由深度从浅到深依次递增。 同时,我们还要求出,从一个点开始,不经过任何轻边,最多到哪个点。 解决询问 而对于一次路径上某值的询问,我们可以把这个路径拆成若干条链。 可以证明链数不超过 $O(\log n)$,这一点会在下面“复杂度证明”中证明。 而这些拆出的链,都是原树中的链的“子集”(一条链 $L_1$ 是另外一条链 $L_2$ 的子集,有且仅当 $L_1$ 中的点,$L_2$ 中也有,边同理)。 所以,可以把一条路径,拆成最多 $O(\log n)$ 个DFS序区间。 ...

2025/02/09 15:28:00

四边形不等式

(四边形不等式看标题可能觉得很难,其实比较简单) 四边形不等式是一种DP优化的技巧,一般针对二维DP进行优化。 在DP中,往往会有多个决策点,而其中就有一个是最优的,设 $d_{i,j}$ 的最优决策点为 $f_{i,j}$。 (以下假设DP枚举决策点的复杂度为 $O(n)$,DP状态数为 $O(n^2)$) 而四边形不等式就是说,如果满足 $f_{i,j-1} \leq f_{i,j} \leq f_{i+1,j}$,那么可以把DP枚举决策点的范围降至 $[f_{i,j-1},f_{i+1,j}]$,然后DP的复杂度就从 $O(n^3)$ 降到了 $O(n^2)$。 而这个(复杂度)怎么证明呢?我们画一下 $f$ 数组的表: (此处,我们以 $i$ 为行号,以 $j$ 为列号,行、列号都从 $1$ 开始编号) 然后,画几个标记: 这些标记指的是,在转移 $d_{1,2}$ 的时候,转移点枚举的左右端点分别就是红箭头指向的两个数。 同时,为了方便观察,此处还画了个蓝色的虚线。 同理,还可以画出 $d_{2,3}$ 对应的标记: 以此类推: 可以发现,其实这时候所有蓝线的长度(蓝线连接的两个数字的差值定义为这个蓝线的长度)之和,最多只有 $n$。 而对于 $d_{1,3}$ 一“类”的标记: 可以发现,其实那些蓝线的长度之和也是最多只有 $n$。 在画出所有蓝线之后: 可以发现,一共就有 $n$ “类”蓝线,一“类”蓝线的长度之和最多 $n$,至此,可以证明复杂度(所有蓝线的长度之和)为 $O(n^2)$。 接下来讲几类四边形不等式解决的经典题型: 第一类:$d_{l,r}=\min { d_{l,k}+d_{k+1,r} | l \leq k \leq r }+w_{l,r}$,合并石子类。 第二类:$d_{i,j}=\min { d_{k,j-1}+w_{k+1,i} | 0 \leq k \leq i }$,区间分割类。 (这两类对应的公式里的 $\min$ 也可以替换成 $\max$) ...

2025/02/09 15:28:00

同余最短路

同余最短路看名字感觉很难,但其实非常简单,也好理解,先说一道题: 题目 原型:洛谷P3403 跳楼机 有一栋 $h$ 层的大楼(层数编号从 $1$ 到 $h$),有 $n+1$ 种移动方式: 回到 $1$ 层 向上走 $x_1 \sim x_n$ 层 楼层不是循环的,你无论如何都无法到达 $h+1$ 层或以上。 最初你可以在任意一层(但也无关紧要),问你能到达多少个不同的楼层。 题解 分类 我们按楼层模 $x_1$ 的余数分类,把楼层 $i$ 分到 $i \bmod x_1$ 类。 这样分类有什么用呢?见下。 定义状态 有了这个分类,我们可以定义状态 $d_i$($i \in [0,x_1)$)代表不用「向上走 $x_1$ 层」操作能到达的、最小的第 $i$ 类楼层是多少。 那么,由于有「向上走 $x_1$ 层」操作,所以 $d_i,d_i+x_1,d_i+2x_1,\dots$(比 $d_i$ 大或相等的第 $i$ 类楼层)都是可达的。 接下来考虑转移这个状态。 转移 其实也很好想,比如用一次「向上走 $x_2$ 层」操作,那么 $d_i$ 可以转移到 $d_{(i+x_2) \bmod x_1}$,转移代价为 $x_2$,所以: $d_{(i+x_2) \bmod x_1}=\min(d_{(i+x_2) \bmod x_1},d_i+x_2)$ ...

2025/02/09 15:28:00

网络流

前言 首先,网络流不是一个算法,而是一个整合包(玩MC玩的),说白了就是网络流是多个算法的统称: 最大流 EK Dinic 最小割 费用流 … 而且,有些不常用的,可能就不会提,粘个OI Wiki的链接就不详细写了。 接下来就按照这个目录挨个讲一下每个算法。 最大流 如果有哪里没有说清楚,可以看这里,里面还有我没有说到的最大流类型。 看到最大流这个名字很多人都很陌生,这里就先从定义说起。 定义 还是拆词法,“网络流”就可以大致理解为“网络”上的“流”,接下来就挨个说一下这两个的定义。 网络 先说网络的定义。(当然不是Internet) 我们在理解的时候可以认为是管道,但实际上它是一张特殊的有向图。 建设管道的必然知道,管道内流过的水,准确来说是单位时间内流过的水,必然是有上限限制的,否则管道就炸了。 这里也一样,对于每条边 $x \to y$,都有一个函数 $c(x,y)$ 表示这条边的限制,又称容量。 如果在图上没有这条边,则统一规定 $c(x,y)=0$。 最后,还是跟最短路一样,它有一个起点和一个终点,又称源点 $S$ 和汇点 $T$($S \not= T$)。 流函数 再说流函数。 流函数就是说,在真实操作中,一个单位时间内一条边 $x \to y$ 流过的水量 $f(x,y)$。 这里默认保证每个单位时间内都得流过这么多。 其实很好理解。 流函数性质 顺便说一下流函数要满足的性质。 首先,源点可以无限输出水,汇点可以无限输入水。 接下来就是一堆性质: 对于每个 $x,y$,显然 $f(x,y) \leq c(x,y)$(容量限制)。 对于每个 $x,y$,$f(x,y)=-f(y,x)$(斜对称)。 对于每个 $x$,满足不是源点也不是汇点,$\sum\limits_{u \to x} f(u,x)=\sum\limits_{x \to v} f(x,v)$(流量守恒)。 这里说一下最后一点性质。 ...

2025/02/09 15:28:00

斜率优化

(说是斜率优化,其实和斜率几乎没有关系) (e…更新后确实有关系) 其实斜率优化和决策二分栈(队列)很像,下面分几种类型来讲。 一维斜率优化 针对形如 $d_i=\min\limits_{j=0}^{i-1} \big( d_j+w(j+1,i) \big)$ 的方程,而且需要满足决策单调性。 此处重新说一下,如果我们发现一个转移点 $x$ 在 $d_i$ 的时候已经比另外一个转移点 $y$ 要更优了($x>y$),那么转移点 $y$ 在后面($d_{i+1}$ 及以后)就不可能比转移点 $x$ 要更优了,那么这个DP一定满足决策单调性,而且反过来也一样(见"决策单调性.md")。 其实证明是否满足单调性只用看:如果一个决策点 $x$ 如果在 $d_i$ 的转移中已经比决策点 $y$ 要优了,那么 $d_{i+1}$ 中是否仍然满足这个条件。 证明了满足决策单调性后,就可以开始斜率优化了。 我们首先要考虑推斜率式: 其实斜率式就是把式子 $d_x+w(x+1,i)<d_y+w(y+1,i)$($x>y$)不断移项、变化,直到式子左边只和 $x$、$y$ 有关,右边只和 $i$ 有关为止。 此时的斜率式一般左边是个分数,而且这个分数还需要把分子和分母的下标对应: 如果斜率式最后是像这样的: $\dfrac{d_x-d_y}{v_y-v_x}>-a_i$ 那么就不是个合法的斜率式,需要变成: $\dfrac{d_x-d_y}{v_x-v_y}<a_i$ 才是合法的。 注意,这里说的是“合法的”,而不是“正确的”,其实最上方的斜率式也可以使用,就是不能叫“斜率式”了(斜率的公式是 $\dfrac{y_2-y_1}{x_2-x_1}$)。 设此时的斜率式就是上面说的: $\dfrac{d_x-d_y}{v_x-v_y}<a_i$ 那么,我们就定义 $\text{slope}(y,x)=\dfrac{d_x-d_y}{v_x-v_y}$。 有了上面函数,显然如果存在两个转移点 $x$ 和 $y$($x>y$)在转移到 $d_i$ 的时候满足 $\text{slope}(x,y)<a_i$,那么就说明转移点 $x$ 比转移点 $y$ 更优,转移点 $y$ 在后面就不需要了。 下面默认推出来的斜率式就是上式,并默认 $a$ 数组单调不降。 ...

2025/02/09 15:28:00

行列式

要说行列式,就得先说一下矩阵(其实是方阵)的秩的定义。 对于一个方阵,其秩的定义就是矩阵里线性无关的极大值。 听这个定义有点难懂,这里解释一下。 其实一个方阵就可以被当做是一个没有右项的方程集。 而一个方阵的秩就可以当做是,这个没有右项的方程集在经过高斯消元后,非 $0$ 行的数量。 有了这个定义,矩阵的秩还可以被当做是,这个没有右项的方程集中,不会被其他方程替代掉的方程数量: 解释一下,如果方程长这样: $ \begin{cases} -x_1+2x_2+5x_3=11 \ 2x_1=8 \ x_1+2x_2+5x_3=19 \end{cases} $ 那么,其实第三个方程式可以通过前两个方程式推出来,也就称作第三个方程式可以被前两个方程式替代掉。 知道了“替代”的定义后,就知道了一件事,就是上面说的“会被其他方程替代掉的方程”,这些方程可以直接从方程组里去掉,而不影响最终的解。 所以,我们就可以对奥数里的一个点做一个更简洁的解释: 在奥数里,讲了解 $n$ 元一次方程(组)的方法。 同时还说了,在这个 $n$ 元一次方程组内,如果“有用”的方程数量,刚好等于未知数的数量,或者大于未知数的数量,那么这个方程组就有唯一解。 这一点可以被更简洁地解释为: 这个 $n$ 元一次方程组,转化为“高斯矩阵”,并删除最后一列后(变成 $n \times n$ 的方阵),如果这个方阵的秩刚好为 $n$,那么这个方程组就有唯一解。 然后,我们回归正题,说行列式。 行列式是对于一个方阵来讲的,一个方阵有行列式有且仅当这个方程的秩刚好等于方阵的大小(行或者列)。 行列式的定义此处不讲,因为: 太复杂了 (几乎)没有用处 行列式的计算方法是用的高斯消元。 在高斯消元的思路里,提到了三种“基本行变换”:(以下全部以高斯矩阵的角度去说) 将一行全部乘上一个非 $0$ 实数 $x$,解不变。 将一行内的所有元素对应地加上/减去另一行对应的元素乘上一个非 $0$ 实数 $x$,解不变。 交换两行,解不变。 这三种基本行变换在执行前后,行列式并不是都不变的: 将一行全部乘上一个非 $0$ 实数 $x$,行列式也会乘上 $x$。 将一行内的所有元素对应地加上/减去另一行对应的元素乘上一个非 $0$ 实数 $x$,行列式不变。 交换两行,行列式变号(负数变成正数,反之亦然)。 所以我们只用写一下高斯消元,然后处理一下对行列式的值的变化即可。 但这儿还有一点问题,就是行列式怎么求。 求法很好记,如果说当前方阵是个上三角矩阵(设方阵叫 $a$,那么 $a$ 是个上三角矩阵有且仅当对于所有的 $(i,j)$,如果满足 $i>j$,那么 $a_{i,j}$ 一定要等于 $0$,当然其他方格不做要求),那么这个方阵的行列式就是这个方阵中正对角线上的元素的乘积。 ...

2025/02/09 15:28:00

一个非常重要的语法点

说一个非常重要的语法错误。 如果你写这样一句话: v[x] = newnode(); 其中: v为一个vector。 x为不越界的非负整数,且不会变化。 newnode()函数内会向vector内push_back一个新元素,并返回加入元素(即最后一个元素)的下标。 那么,你会发现,v[x]的值并不是这样变化的: 1,2,3,4,5,6,7,... 而是类似于这样的:(非真实输出结果,仅供参考) 1,1,2,2,3,4,4,5,6,7,8,8,9,10,11,12,13,14,15,16,16,17,18,19,20,... 你会发现部分情况下,v[x]没有变化! 这实际上是因为vector的内部机制。 vector会有一个capacity,就是提供的内存大小。 每次push_back的时候,如果说加入后,不超过当前的capacity,就会直接加入。 否则,vector会新开辟一块等于当前capacity的两倍(这个在不同编辑器上是不一样的,有些可能是每次新增 $5$ 个)的新空间,并把原本的数据拷进这块新空间内。 比如说现在的vector是: 内存354~357位置:3 1 2 4 且capacity是 $4$,那么我们如果尝试push_back一个 $5$,则vector就会发现大于了capacity,便会新开一块等于当前capacity的两倍的空间: 内存354~357位置:3 1 2 4 内存382~389位置: 并把当前数据拷进去: 内存354~357位置:3 1 2 4 内存382~389位置:3 1 2 4 然后在新空间内加入 $5$: 内存354~357位置:3 1 2 4 内存382~389位置:3 1 2 4 5 并把所有v[x]指向的内存都改成382~389这块新内存内对应的地方。 不过由于C++的赋值机制,系统会先找到要赋值给的变量的指针,然后求出要赋的值,赋给以前找到的指针。(这一步是推测,可能不准确) 所以就会导致把newnode()的值赋给了原本的v[x],导致输出的时候访问的新v[x]值出错。 解决方法也很简单,把newnode()用个临时变量t存储,并将v[x]赋值为t即可。

2025/02/09 15:28:00

一元二次方程解法(未完工)

在数学、编程界里,可能会经常遇到方程,而且有时候还会遇到一种特殊方程——一元二次方程。 除此之外,还有含参一元二次方程,这里也会一起讲。(待补充) 其实还有如二元一次方程、一元三次方程等的特殊方程,但在这里不讲。 一元二次方程 有人看到“一元二次方程”就不知道是啥意思,这里先说一下定义 定义 首先,方程相信大家都见过,所以这里不写对方程的定义解释。(废话 而 $x$ 元 $y$ 次方程就是这么定义的: 这个方程经过化简后,共有 $x$ 个未知数,且未知数上的最大指数为 $y$ 的方程。 如: $3(x+1)=6x$:一元一次方程,也是最常见的方程。 $3(x+1)^2=2(x+1)$:一元一次方程,因为两边可以同时除以 $x+1$ 以让指数减少 $1$。 $x^2-4x+1=0$:一元二次方程,也是下面要讲的方程类型。 $x^3+2x^2-1=16x$:一元三次方程,也是一种不常见的方程类型。 讲完定义,接下来说怎么解 解法 推导 我们把方程一般化,变成: $ax^2+bx+c=0$ 其中 $a$、$b$、$c$ 均为常量。 然后,我们不断对方程做一些推导。 移项 $ax^2+bx=-c$ 同时除以 $a$ 常量 $x^2+\dfrac{b}{a}x=-\dfrac{c}{a}$ 这个式子很多人看了就不知道怎么继续推导了。 但还是可以继续转化的。 我们考虑配方,即把式子的左/右项同时加/减/乘/除一个相同的数,然后让这个等式的左/右项变成一个数的平方的形式。 这一步比较考验数感,一个结论就是,我们同时加上 $\left( \dfrac{b}{2a} \right)^2$ 常量 即可。 $x^2+\dfrac{b}{a}x+\left( \dfrac{b}{2a} \right)^2=-\dfrac{c}{a}+\left( \dfrac{b}{2a} \right)^2$ 化简 $\left( x+\dfrac{b}{2a} \right)^2=\dfrac{-c}{a}+\dfrac{b^2}{4a^2}$ 二次化简 $\left( x+\dfrac{b}{2a} \right)^2=\dfrac{b^2-4ac}{4a^2}$ 同时开根号 $x+\dfrac{b}{2a}=\sqrt{\dfrac{b^2-4ac}{4a^2}}$ ...

2025/02/09 15:28:00

整除分块

感谢这篇文章的作者 整除分块主要解决求解 $\sum\limits_{i=1}^n \lfloor \frac{n}{i} \rfloor$ 的值。 一看这个公式,可能毫无头绪,但画张图你就有点思路了:($n=11$) (灰色线:$y=\frac{11}{x}$,绿色点:$y=\lfloor \frac{11}{x} \rfloor$,橙色线:仅为辅助) 这张图其实就是在提示我们: $\lfloor \frac{n}{i} \rfloor$ 的值随着 $i$ 的增长而非单调递减 $\lfloor \frac{n}{i} \rfloor$ 的值往往会在一段区间内相等 所以,我们就有一个思路了,就是求出所有值(下面的“值”都代指的是 $\lfloor \frac{n}{i} \rfloor$)相等的极大区间集,然后就可以快速统计了。 有人问,这不就是个常数级别的优化吗,怎么能叫“快速”呢?其实区间个数总是 $O(\sqrt{n})$ 级别,最多约 $2 \times \sqrt{n}$。 证明: 我们把 $i$ 分成两部分去考虑,一部分是 $i \leq \sqrt{n}$,另一部分是 $i>\sqrt{n}$。 在第一部分内,$i$ 也就只有 $\sqrt{n}$ 种取值,$\lfloor \frac{n}{i} \rfloor$ 最多只有约 $\sqrt{n}$ 种取值。 在第二部分内,$\lfloor \frac{n}{i} \rfloor$ 在 $i$ 最小时($i=\sqrt{n}+1$)会达到最大值,约 $\sqrt{n}$;而上面说“$\lfloor \frac{n}{i} \rfloor$ 的值随着 $i$ 的增长而非单调递减”,所以易得 $\lfloor \frac{n}{i} \rfloor$ 最多也只有约 $\sqrt{n}$ 种取值。 $\lfloor \frac{n}{i} \rfloor$ 的总取值种数最多只有第一、二部分的取值个数和,即 $2 \times \sqrt{n}$,证毕。 ...

2025/02/09 15:28:00