CSP初赛复习
一.存储知识:
1.存储:
编码:
比特(bit):编码最小的单位。
字节(byte):存储的最小单位。
2.存储单位的转化:
1 KB = 1024 Byte
1 MB = 1024 KB
1 GB = 1024 MB
1 TB = 1024 GB
3.常见的变量类型的存储大小:
1个 int = 4个 Bytes
1个 (unsigned) long long = 8个 Bytes(unsigned long long 不存负数,存非负数是long long 的两倍)
1个 bool = 1个 Byte
1个 char = 1个 Byte
1个 float = 4个 Bytes
1个 double = 8个 Bytes
二.计算机系统的组成:
硬件系统(hardware)+软件系统(software)。
1.硬件系统:
(1):硬件系统包括主机(CPU、内存...)以及外接设备(输入输出设备等)。
(2):内存分为 RAM(可读写存储器,掉电之后数据清除) 、ROM(只可读存储器,掉电之后数据保留) 和 Cache(高速缓冲存储器,为了适应CPU的高速计算) 三种。
(3):register(寄存器)是CPU里的一个小区域,用来存变量。一般变量存在内存,而 register 可以将变量存到CPU里去,以达到加快运行速度的目的,但一般用于存大量重复的变量,例如循环变量:for(register int i=1;i<=n;i++)
之类的。
2.软件系统:
软件系统包括操作系统(Linux、Windows、Mac等)以及应用软件(APP)。
三.进制转化:
(1)十进制转 \(m\) 进制——短除法:
如图示:
(2) \(m\) 进制转十进制——逐位乘后求和:
如:
\((10110)_2 = 1 \times 2^4 + 0 \times 2^3 + 1 \times 2^2 + 1 \times 2^1 + 0 \times 2 ^0 = (14)_{10}\)
四.位运算和位移操作:
1.位运算(支持各种运算率):
优先级:括号 \(>\) 四则运算 \(>\) 逻辑运算 \(>\) 位运算
(1):按位取反,符号~
,将一个整数在二进制下逐位取反。
(2):按位与,符号&
,将两个整数在二进制下对齐个位逐位比较,数码同为 \(1\) 则为 \(1\),否则为 \(0\)。
(3):按位或,符号|
,将两个整数在二进制下对齐个位逐位比较,数码同位有 \(1\) 则为 \(1\),否则为 \(0\)。
(4):按位异或,符号^/xor
,将两个整数在二进制下对齐个位逐位比较,数码不同则为 \(1\),否则为 \(0\)。
2.位移操作:
(1):左移: 符号<<
,左移一位相当于 $ \times 2 $ 。
(2):右移: 符号>>
,右移一位相当于 $ \times \dfrac{1}{2}$。
五.编码:
(1):编码包括原码、反码、补码。
(2)原码:正负用符号位区分,正数第一位为 \(0\),后接数值,负数则为 \(1\)。
eg: \(5\) 原码为 \(0\ 101\)。\(-5\) 原码为 \(1\ 101\)。
(3)反码:正数的反码就是原码,负数的反码是符号位不变,数值部分取反。
eg: \(-5\) 反码为 \(1\ 010\)。
(4)补码:正数的补码还是本身,负数的补码是反码 \(+1\)。
例题:在8位二进制补码中,\(10101011\) 表示十进制下的_____?
\(ans:\) -85。
六.数据结构:
(1):初赛常考:线性数据结构、树形数据结构、图类数据结构。
(2)线性数据结构: 数组(包括静态数组和动态数组)、队列(先进先出)、栈(后进先出)、链表(一环扣一环、无法直接访问任意元素)。
栈常考进出栈顺序及前后缀表达式。
前缀表达式:指将四则运算的符号放在最前面,两个数值放在最后面的表达式。
后缀表达式:指将四则运算的符号放在最后面,两个数值放在最前面的表达式。
例题:
\(a - b \times c + d \times (f - e)\) 的前缀表达式: \(+\ -\ a \times b\ c \times d -f\ e\)
\(a - b \times c + d \times (f - e)\) 的后缀表达式:\(a\ b\ c \times -\ d\ f\ e\ - \times \ +\)
(3)树的概念:
树是一种特殊的图,所有结点连通,且 \(n\) 个点 \(n-1\) 条边,树中一定没有环。
在树的结点中,包含父节点、子节点(相对关系)。
根节点是没有父节点的节点,根据是否约定根节点,分为有根树(约定了根节点或边有向的树)或无根树(没有约定根节点且边无向的树)。
叶节点是没有子节点的节点。
(4)特殊的树形结构:
二叉树:对于每个节点,最多只有两个子节点的树。
完全二叉树:除了最后一层,是一颗完美二叉树,且最后一层的节点尽量靠左分布的二叉树。
二叉搜索树:对于任意一个节点 \(x\),其左子树中的权值均不超过 \(x\) 的权值,其右子树中的节点权值均超过 \(x\) 的权值的二叉树。
(5)二叉树中的一些性质:
树的深度(高度):描述一棵树的节点层数,需要约定根节点是 \(0\) 还是 \(1\)。
二叉树中的数量性质:
1.若深度从 \(1\) 计算,则二叉树第 \(i\) 层上最多有节点 \(2^{i-1}\) 个。
2.:若深度从 \(1\) 计算,则深度为 \(h\) 的二叉树最多有 \(2^h-1\) 个节点
3.:若深度从 \(1\) 计算,那么 \(n\) 个节点的二叉树深度至多为 \(n\),至少为 \(\lceil \log_{n+1}\rceil\)。
4.:所有的二叉树都可以直接用数组来存储,若节点编号为 \(i\),则下标也为 \(i\),且左孩子为 \(i \times 2\),右孩子为 \(i \times 2 + 1\)。
(6)树的遍历:
1.前序遍历(根左右):对于每个节点 \(x\),满足先输出 \(x\),再输出 \(x\) 的左孩子,最后输出 \(x\) 的右孩子。
2.中序遍历(左根右):对于每个节点 \(x\),满足先输出 \(x\) 的左孩子,再输出 \(x\),最后输出 \(x\) 的右孩子。
3.后序遍历(左右根):对于每个节点 \(x\),满足先输出 \(x\) 的左孩子,再输出 \(x\) 的右孩子,最后输出 \(x\)。
4.层序遍历:从上到下,从左到右。
5.由二叉树的形态可以唯一得到遍历顺序,反之不行,但 先序+中序 或 后序+中序 可以唯一确定二叉树的形态。
代码实现:
#include<bits/stdc++.h>
using namespace std;
struct Node
{
int lc,rc;
}a[MAXN];
inline void dfs1(int x)//前序遍历
{
if(x==0)
return;
printf("%d\n",x);
dfs1(a[x].lc);
dfs1(a[x].rc);
return;
}
inline void dfs2(int x)//中序遍历
{
if(x==0)
return;
dfs2(a[x].lc);
printf("%d\n",x);
dfs2(a[x].rc);
return;
}
inline void dfs3(int x)//后序遍历
{
if(x==0)
return;
dfs3(a[x].lc);
dfs3(a[x].rc);
printf("%d\n",x);
return;
}
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int n;
cin>>n;
for(register int i=1;i<=n;i++)
cin>>a[i].lc>>a[i].rc;
dfs1(1);//均以1为根
dfs2(1);
dfs3(1);
return 0;
}
例题 P1030 求先序排列
Code
#include<bits/stdc++.h>
using namespace std;
inline void dfs(string z,string h)
{
if(z.size()==0)
return;
char rt=h[h.size()-1];
int p=z.find(rt);
cout<<rt;
dfs(z.substr(0,p),h.substr(0,p));
dfs(z.substr(p+1),h.substr(p,h.size()-p-1));
return;
}
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
string z,h;
cin>>z>>h;
dfs(z,h);
return 0;
}
例题 P1827 美国血统 American Heritage
Code
#include <bits/stdc++.h>
using namespace std;
string a,b;
inline void dfs(int x,int y,int p,int q)
{
if(x>y || p>q)
return;
else
{
int i=b.find(a[x]);
dfs(x+1,x+i-p,p,i-1);
dfs(x+i-p+1,y,i+1,q);
cout<<a[x];
}
}
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>b>>a;
int len=a.length()-1;
dfs(0,len,0,len);
return 0;
}
(7)图的概念:
由点和线构成的网络。
(8)图的分类:
有四种方式:
1.可分为有向图(边有向或有箭头)和无向图(边无向或无箭头)。
2.可分为有权图(边有权重)和无权图(边没有权重)。
3.可分为有环图(存在至少一个环)和无环图(不存在环)。
4.可分为稀疏图(点多边少)和稠密图(边多点少、边很密集)。
(9)图顶点的度数:
1.有向图:分为入度(一个点被多少箭头指向)和出度(一个点指出的箭头数量)。
2.无向图:一个点连接的边的数量(无向图不分入度出度),
(10)图的连通性:
若图中有点 \(x\) 能够找到一条路径到达 \(y\),则称 \(x\) 与 \(y\) 连通。
完全图:图中的每一个点都与其余所有点有直接连边。
对于有 \(n\) 个点的无向完全图,其边数为 \(\dfrac{n \times (n-1)}{2}\)
对于有 \(n\) 个点的有向完全图,其边数为 \(n \times (n-1)\)
(11)图的存储:
1°邻接矩阵,设二维数组 \(g_{i,j}\) 表示点 \(i\) 指向点 \(j\) 的一条边。
2°邻接链表,设动态数组vector<int>nbr[105];
比如 \(2\) 到 \(5\) 有一条边,那么nbr[2].push_back(5)
。
也可用链式前向星:
struct edge
{
int to,nxt,len;
}e[MAXN<<1];
int head[MAXN],cnt;
inline void add(int x,int y,int z)//从点x到点y连一条权值为z的边
{
e[++cnt].to=y;
e[cnt].len=z;
e[cnt].nxt=head[x];
head[x]=cnt;
return;
}
七.排序算法:
(1)冒泡排序:时间复杂度 \(O(n^2)\),稳定。
(2)选择排序:时间复杂度 \(O(n^2)\),不稳定。
(3)插入排序:时间复杂度 \(O(n^2)\)(可以做到 \(O(n)\)),稳定。
八.数学相关的问题:
(1)加法原理:做一件事情,有 \(n\) 种做法,第 \(i\) 种做法有 \(a_i\) 种方式,则完成这件事情有 \(\sum\limits_{i=1}^n a_i\) 种不同的方法。
(2)乘法原理:做一件事情,有 \(n\) 个步骤,第 \(i\) 个步骤有 \(a_i\) 种方式,则完成这件事情有 \(\prod\limits_{i=1}^n a_i\) 种不同的方法。
(3)排列数公式:在 \(n\) 个数种选 \(m\) 个数构成 \(1\) 组有序的排列,方案数为:
(4)组合数公式:在 \(n\) 个数种选 \(m\) 个数构成 \(1\) 组无序的组合,方案数为:
例1: \(8\) 个人排成一队。
1.要求甲乙两人必须相邻。
解:
2.甲乙不相邻。
解:
3.甲乙相邻但是与丙不相邻。
解:
4.甲乙相邻且丙丁相邻。
解:
例2:有人射击,开了 \(8\) 枪,命中 \(4\) 枪,恰好有 \(3\) 枪连续命中的方案有多少?
解:在五个空中选取 \(2\) 个排列,即 \(A_5^2\)。
(5)卡特兰数(Catalan):
\(C_n\) 表示在 \(n+2\) 条边的凸多边形中,可以画 \(n-1\) 条不相交的对角线,将该多边形分成 \(n\) 个三角形。
\(C_n\) 也可表示所有在 \(n \times n\) 格点中不越过对角线的单调路径的个数。
递推式:
通项公式一:
通项公式二:
通项公式二其实就是把通项公式一拆开。
那么我们这里来证一下第二个:
根据定义,合法的路径不能越过对角线。那么我们考虑利用总方案数 \(C_{2n}^n\) 减去不合法的方案数。
这里的黄色线和绿色线连接而成的路径代表一条不合法的路径,我们把路径第一次超越对角线的点(点 \(L\))到终点(点 \(A\))的路径沿粉色线条(对角线向上平移 \(1\) 个单位得到)对称。得到了蓝色的路径。显然,这里的黄色路径和蓝色路径是对应的。
而之所以选择“向上平移 \(1\) 个单位”的意义就是在任何情况下接触这条线就会变成不合法路径。
容易证明,所有不合法的路径和所有从原点到A'点的路径都是一一对应的。所以不合法的路径条数就是从原点到A'的路径条数 \(C_{2n}^{n-1}\)
证明如下:
因为这个关系是可逆的(通过操作后的路径能得出操作前的路径),所以显然所有不合法的路径和所有从原点到 \(A'\) 点的路径都是一一对应的。
这样就得出了前面提到的卡特兰数第二个通项公式:
那么现在我们来应用一下:
例1:有一个算式,包含 \(n\) 运算符及 \(n+1\) 个运算数值,要求在算式中加上 \(1\) 对括号,求不同的运算顺序有多少种?
解:这种题目基本用卡特兰数解决,结果就是一个二叉树。
例2:有 \(n\) 个节点的二叉树,不同的形态有多少种?
解:很明显我们分别考虑左右子树,那显然就是卡特兰数的递推式。
放球问题:有 \(n\) 个球放入 \(m\) 个盒子中:
1.球同,盒子相同,不允许空盒子;(只能穷举法)
2.球同,盒子相同,允许空盒子;(只能穷举法)
3.球同,盒子不同,不允许空盒子;(隔板法)\(C_{n-1}^{m-1}\)
4.球同,盒子不同,允许空盒子;(隔板法)\(C_{n+m-1}^{m-1}\)
5.球不同,盒子相同,不允许空盒子;(第二类斯特林数)
6.球不同,盒子相同,允许空盒子。(第二类斯特林数)
第二类斯特林数:\(\begin{Bmatrix}n\\m\end{Bmatrix}\) 表示 \(n\) 个不同元素分为 \(m\) 个集合的方案数。
递推式:
说白了就是第 \(n\) 行第 \(m\) 个数等于它上一行第 \(m-1\) 个数加上一行第 \(m\) 个数乘 \(m\)。
通项公式:
第一类斯特林数:\(\begin{bmatrix} n\\m\end{bmatrix}\) 表示 \(n\) 个不同元素分为 \(m\) 个圆排列的方案数
有标号的第一类斯特林数:\(s(n,m)=(-1)^{n-m}\begin{bmatrix} n\\m\end{bmatrix}\)
递推式:
即新建一个圆排列,或者插入前面 \(n-1\) 个元素中任意一个的后面。
那么显然,有标号的就是:
生成函数有些复杂,这里就不说了。