NOTES

二叉树

对于任意一棵二叉树,满足度数为 \(2\) 的节点数是叶子数量减 \(1\)

一棵度数为 \(k\) 的树,设度数为 \(1,2,3\dots\) 的节点数分别为 \(w_1,w_2,w_3\dots\) 该树节点数为 \(\sum\limits_{i-1}^k w_i\times i + 1\)。原理:一棵树的节点数量 \(n\) 等于所有儿子数量 \(+1\),即只有 root 不能作为儿子。且儿子的统计显然不会重复。

例1:一棵度数为 \(3\) 的树 \(T\)\(3\) 个度数为 \(1\) 的节点,\(2\) 个度数为 \(2\) 的节点,\(3\) 个度数为 \(3\) 的节点,求叶子数量。

题目给的度数挺全,可以求出 \(n=3\times 1+2\times 2+3\times 3+1=17\)

然后叶子即为 \(n-(3+2+3)=9\)

例2:一棵二叉树有 \(2024\) 个节点,且拥有一个孩子的节点多于叶子,则拥有两个孩子的节点至多有多少个。

显然我们要依据题目限制列不等式求解。本题限制为“拥有一个孩子的节点多于叶子”。设有两个儿子的节点个数为 \(a\),则叶子个数为 \(a+1\)。显然满足如下关系。

\[2024-a-(a+1)>a+1 \]

解得 \(a<674\)。即 \(a\) 最大取 \(673\)

二叉树前序,中序,后序遍历问题。

  • 概念

    • 前序:中左右
    • 中序:左中右
    • 后序:左右中
  • 前序,中序,后序确定二叉树问题。

方法:前序定根,中序后序定左右。因此,给定前序,再给中序或后序是可以确定一棵唯一的二叉树的看,仅给定前序和后序是非法的。

数学相关

对数换底公式:\(\log_b^N=\dfrac{\log_a^N}{\log_a^b}\)

例如,\(\log_2^N=\dfrac{\log N}{\log 2}\)。(\(\log\) 默认以 \(10\) 为底。)若需上取整需要 \(+1\)。这是以 \(10\) 为底的 \(\log\) 和以 \(2\) 为底的 \(\log\) 转化。常考于倍增上界问题。

Claim: \(2^0+2^1+2^2+2^3\dots +2^{n-1}=2^n-1\)

Proof:

简单构造即可。

\(S=2^0+2^1+2^2\dots 2^{n-1}\),则 \(2S=2^1+2^2+2^3\dots +2^n\)

\(2S-S\),得:

\(2^n-1\)

继而得 \(S=2^n-1\)

应用:一棵高度为 \(n\) 的满二叉树有 \(2^0+2^1+2^2\dots 2^{n-1}=2^n-1\) 个节点。


进制转换。

常考的有二进制,十进制,十六进制,八进制。

\(n\) 进制转十进制:考虑进制意义。例如,十进制数 \(114514=1\times 10^5+1\times 10^4+4\times 10^3+5\times 10^2+1\times 10^1+4\times 10^0\)

十进制转 \(n\) 进制:不断除以 \(n\),直到商为 \(0\),倒序取余数。

对于小数部分,不断将小数部分乘 \(n\),正序取整数,直到小数部分为 \(0\)

特殊的,二进制转十六进制:从右往左取,每四位二进制转换成十进制,再转换成对应的六进制(如十进制下 \(11\) 为十六进制下 B)。

理论

紧急插播一张图片。

插入冒泡归并桶,还有基数和计数。(稳定。)

CPU 访问速度排序:寄存器 > 高速缓存(cache)> 内存 > 外存

WAN (Wide Area Network):广域网(外网),是连接了多个 LAN 或者其他网络的大型计算机网络。

LAN:局域网。

BIOS 全称是 计算机基本输入输出系统。装在 ROM 上。不可修改。

用来全面管理计算机硬件和软件资源的软件是操作系统。

在操作系统中,“中断”指硬件或软件发送信号来打断当前执行的程序,以响应事件或进行任务调度。

计算机内部用来传送,存储,加工处理的数据或指令是以二进制进行,但不一定是机器码。

img

最早的 CPU 是由 Intel 发明的。

注意一些进制的前缀,0x 是十六进制,0 是八进制。0b是二进制。然后就能写一些奇怪的东西:

\(1+01\) 是十进制的 \(1\) + 八进制的 \(1\),答案是 \(2\)

\(0x10+01\) 是十六进制转十进制的 \(16\) + 八进制转十进制的 \(1\) = \(17\)

需要注意的是,计算机输出是在十进制意义下的。

计算机系统内最小信息单位是 Bit。满足 \(8\) Bit = \(1\) Byte。

RAM 是易失性存储器,断电后数据会丢失。而 ROM 是只读性存储器,数据是永久的,不可修改。

四进制数转 \(16\) 进制。四进制的相邻两位可以转换成十六进制。

将 unsigned x 的高 \(16\) 位和低 \(16\) 位交换:(x<<16)|(x>>16)

x<<16\(x\) 的高十六位,其余位置是 \(0\)x>>16\(x\) 的低 \(16\) 位,其余位置是 \(0\)。位运算相关内容需要再看。

bmp 格式图片占用空间规则:

  • 24 位,32 位的 bmp 文件不包含调色板,而调色板需要 \(4\times 2^n\) Byte。

若不包含文件头。bmp 文件大小 = width * height * n/8 (\(n\) 为位数,\(8\) 为位深。)

因此,\(2560\times 1440\)\(32\) 位彩色照片,采用 bmp 格式存储,占用的空间大小为 \(2560\times 1440\times 32\div 8\div 1024\div 1024=14.1 \operatorname{Mib}\)

Linux 命令相关

  • mkdir 创建文件夹。

  • touch 创建文件。

  • ls 显示目录。

  • mv 移动或重命名文件夹和目录。

  • cat 连接一个或多个 <文件> 并输出到标准输出。

  • vi 是编辑命令,会调出 vim,more 是分页显示,cat 是普通显示,ls 是显示文件和文件夹。

gdb 里面不进入函数的单步执行语句是 next 或 n,进入函数的单步执行语句是 step 或 s,breakpoint 或 b 是设置断点,continue 或 c 是继续执行语句。

img

real 算上了用户交互时间,user + sys 是程序实际执行时间。

显然 real 不一定等于 user + sys

基础 ds

双向链表删除,查找一个元素是 \(O(n)\) 的。但插入,删除一个元素是 \(O(1)\) 的。

单向链表:只能一个方向遍历

双向链表:既可以倒着扫,也可以正着扫。

循环链表:最后一个节点指向第一个节点。

链表不必事先估计空间,插入删除不需要移动元素,所需空间与线性表长度成正比。

还有一个考法是链表操作。例如

img

A

按照题意模拟即可。不能选 B,会覆盖原来 head 的值。

一般的,我们认为链表不支持随机访问。

注意到栈要求 “先进后出”,而队列要求“先进先出”。二者恰好相反。我们可以用两个 stack 模拟队列,具体的,开两个stack \(s_1,s_2\),每次入队时,将元素压入 \(s_1\),出队时将 \(s_1\) 内所有元素弹出并压入 \(s_2\)。按要求弹出 \(s_2\) 内元素即可。

需要注意的是,\(s_2\) 为空时,方可从 \(s_1\) 中新压入元素。这是因为原 \(s_2\) 中元素优先级全部比新元素高。若提前压入会破坏优先级。

这玩意复杂度是很劣的,入队复杂度 \(O(1)\),但出队复杂度为 \(O(n)\)

链表问题。

分为单向链表和双向链表,为了节约空间,还有循环链表。

  • 定义

例:

struct Node
{
    int val;
    Node *pre;
    Node *nxt;
};

上述代码定义了一个双向链表。\(val\) 存储了当前节点元素值,指针 pre 和 nxt 分别指向前驱和后继的 地址

单向链表的定义仅需在此基础上删除前驱即可。

循环链表:尾节点的 nxt 指向链头。是一种单向链表,从任意一个节点出发均可遍历所有节点。

链表的遍历是 \(O(n)\) 的,但可很方便进行删除节点,添加节点操作,一般不支持随机访问。

考点:链表基本操作(插入,删除)

例:双向链表有一个两个指针域,llink 和 rlink。分别指向前驱和后继。现有指针 p 指向链表中的一个节点,q 指向一待插入节点。要求在 p 前插入 q,写出正确操作。

Solution

p->link->rlink=ql;q->rlink=p;q->link=p->link;p->link=q;

在纸上画出链表示意图即可解题。还会考察链表的删除,很简单。

邻接表存图法指

constexpr int N = 1000010;
vector <int> Edge[N];
int u,v;
cin>>u>>v;
Edge[u].push_back(v);
Edge[v].push_back(u);

当然拿链表写也是可以的。

而邻接矩阵一般指

constexpr int N = 1010;
int m[N][N];
int u,v,w;
cin>>u>>v>>w;
m[u][v] = w,m[v][u] = w;

而这样的

constexpr int N = 1000010;
struct Node
{
    int u,v,w;
};
vector <Node> Edge;
int u,v,w;
cin>>u>>v>>w;
Edge.push_back({u,v,w});

即 kruskal 给边排序时存边方式,既不是邻接表也不是邻接矩阵。同理,用结构体也是一样的。

哈夫曼树

这是一种字符串编码方式。该编码方式保证了 任意字符编码不是另一个编码的前缀,即不会产生歧义,且所有字符的编码拼起来后长度最小。

具体的,我们将所有字符的 出现频率 扔到一个大根堆里,每次取出堆里最小的两个元素 \(u,v\),将二者与新节点连边,且新节点点权为 \(u+v\),最后将 \(u+v\) 扔回大根堆。

哈夫曼树是一棵二叉树,且不存在儿子个数为 \(1\) 的节点。原因显然,除了叶子,每个节点都是由两个节点“合并”得到的。

常考形式包括但不限于给定每个字符及其出现频率,求哈夫曼编码;利用哈夫曼树解决问题。甚至阅读程序/完善程序可能出哈夫曼树板子或相关内容,若不了解哈夫曼树模拟会很困难,几乎不可做。

例题: 现有一份仅包含 \(1000\) 个英文小写字母的文件,使用哈夫曼编码方式对英文小写字母进行不定长二进制编码,发现字母 e 的编码长度为 \(1\)。那么在这份文件中,e 至少出现几次?

A. 39 B. 334 C. 500 D. 501

e 的哈夫曼编码长度为 \(1\) 表明 e 是最后合并的。即 e “直属” root。哈夫曼树构造的本质是若干棵哈夫曼树的不断合并(初始化每个字符独立成树)。设最后有三棵哈夫曼树,其中一棵是 ee 的点权显然比另外两棵哈夫曼树要大(或三个点权都相等)。而点权即为出现次数,另外两棵哈夫曼树的点权分别为下面字符的出现次数和,三棵树权值和显然 = \(1000\)

接下来看选项,A 显然不合法,\(39\times 3<1000\)。对于 B\(334\times 3=1002\),是可以的。C,D 选项显然也是可以的。所以答案为 C

图论

简单题:一个连通的简单无向图,共有 \(28\) 条边,至少有几个顶点。

Solution

让点最少,就让边最多。完全图的边是最多的。

回顾知识:完全图指每个节点都往其他的点连边。有向完全图的边数是 \(n\times(n-1)\),无向完全图的边数是 \(\dfrac{n\times(n-1)}{2}\)。本题中讨论的是无向图。

列出方程:\(\dfrac{n\times(n-1)}{2}=28\)

\(n=8\)

无向图中的环最少包含一个顶点(自环),也可以两个或者更多。只要一个路径的第一个顶点和最后一个顶点相同,就称为环。

有向图的环也至少包含一个顶点(自环),可以两个或更多。

有向图中,若任意两个点之间存在一条边,不一定是强连通图。无向图就对了。

Claim:有向图中所有顶点入度和出度总和是图的边数两倍。

Proof:

对于 \(\forall\)\((u,v)\),设方向为 \(u\)\(v\),则该边对 \(u\) 贡献一个出度,对 \(v\) 贡献一个入度。即每一条边会贡献两次。

神秘语法

函数传参问题。

void qwq(int *a[]); //传递一个指针数组,每个指针指向二维数组每一行的起始。
void qwq(int **a); //传递一个指向指针的指针。外层指针指向“指向二维数组各行起始”的指针数组的首元素。与上面等价。
void qwq(int a[][3]); // 这是可行的
void qwq(int a[][]);void qwq(int a[3][]) // 都是不行的 必须规定列长度
char s[];
char *t = s;
while(~scanf("%s",t)) t += strlen(t);

这个东西可以拼接字符串。

img

高度为 \(n\) 的满二叉树有 \(2^0+2^1+2^2+\dots +2^{n-1}=2^n-1\) 个节点。证明见数学部分。

img

dfs 时注意回溯,记得消除影响全。

遇到一些比较难的组合数学,大分类讨论的选择题,放弃。不差那两分。不要在防 ak 题上浪费时间。

posted @ 2024-09-19 15:09  SXqwq  阅读(5)  评论(0编辑  收藏  举报