OI算法复(学)习笔记
双端队列
双端队列是指一个可以在队首/队尾插入或删除元素的队列构造使用deque<int> d即可
用法如下:
-
q.front()返回队首元素 -
q.back()返回队尾元素 -
q.push_back()在队尾插入元素 -
q.pop_back()弹出队尾元素 -
q.push_front()在队首插入元素 -
q.pop_front()弹出队首元素 -
q.insert()在指定位置前插入元素 -
q.erase()删除指定位置的元素 -
q.empty()队列是否为空 -
q.size()返回队列中元素的数量
例题:双向动态列车
题目描述
想象一下一列动态列车,这种列车特别之处在于它的首节和末节车厢都可以进行装载和卸载操作。这意味着乘客可以从列车的前端或后端上车,也可以从任何一端下车。
你将获得一系列关于乘客上车和下车的操作,你的任务是在所有操作完成后报告列车最终的乘客状态。
操作指令格式:
-
LIN X— 将乘客X从列车的前端上车; -
RIN X— 将乘客X从列车的后端上车; -
LOUT— 从列车的前端让一位乘客下车; -
ROUT— 从列车的后端让一位乘客下车。
输入格式
第一行包含一个整数M(M≤10,000M)表示操作的数量。
接下来的M行,每行包含一个操作指令。
输出格式
输出的第一行应描述在进行了M次操作之后列车的状态。从列车的前端到后端输出乘客编号,每两个乘客编号之间用一个空格隔开。
如果存在任何非法的操作(如尝试让乘客从一个空列车的端下车),这些操作的错误应在输出中得到相应的指示。每个非法操作的格式为“X ERROR”,其中X是该操作的序号(从1开始计算)。
解答
分析
使用双端队列进行模拟即可
标程
1 | |
字符串相关用法
string s.substr(int a, int l)可以从s的a(从0开始)截取一个长度为l的字符串
string s.erase(int p,int n) 删除s从p开始的n个字符
string s.insert(int p,string ss) 在s的p位置插入字符串s s
sprintf(char数组s,"%d",int n) 将n转换放置到char数组s中
sscanf(char数组s,"%d",int &n) 将char数组s转换放置到n中
reverse(v.begin(),v.end())或reverse(a+begin,a+end) 反转范围
并查集
引入
有时,你需要知道多个东西之间有没有关系,而时间和空间限制不允许你大手大脚,并查集便应运而生
简介
并查集是一个树形结构,支持合并和查询,通过储存每个点的父亲实现各种操作。而又因为只需要知道各个点之间有没有关系,所以可以随意选取一个根节点,并且判断操作只需要判断各个点是否在同一棵树内就可以完成,而我们只要比较各点所在树的根节点是否相同就可以,所以只需要存储各点的父亲即可递归查询到根节点进行判断了。
初始化
每个点开始时应当各自为树,各无关系。为了方便我们将父亲为自己的点定义为根节点,因此此处将各个点的父亲定义为自己。
1 | |
查询
查询是为了找到根节点,很显然可以通过递归查询父亲进行解决。
1 | |
路径压缩
递归还是太浪费时间了,仔细一想我们似乎不需要建立一个常规的树形结构,既然只需要知道每个点的根节点,我们可以直接把每个点连接到根节点上,而这个操作可以边查询边进行。
1 | |
合并
将一棵树的根节点连到另一棵树的根节点就好了,当然,由于这个合并操作过于简单,很多情况下也不会将其写成函数。
1 | |
以上就是并查集的基础要点了,做一道模板题试试水吧。
例题1:P3367 【模板】并查集
P3367 【模板】并查集
题目背景
本题数据范围已经更新到 ,。
题目描述
如题,现在有一个并查集,你需要完成合并和查询操作。
输入格式
第一行包含两个整数 ,表示共有 个元素和 个操作。
接下来 行,每行包含三个整数 。
当 时,将 与 所在的集合合并。
当 时,输出 与 是否在同一集合内,是的输出
Y ;否则输出 N 。
输出格式
对于每一个 的操作,都有一行输出,每行包含一个大写字母,为 Y 或者 N 。
输入输出样例 #1
输入 #1
1 | |
输出 #1
1 | |
说明/提示
对于 的数据,,。
对于 的数据,,。
对于 的数据,,。
对于 的数据,,,,。
解答
分析
模板题,照搬以上讲解内容即可。
标程
1 | |
当然,单独的并查集确实简单,但是要注意识别和与其他知识点的合用。
例题2:U149298 搭配购买
U149298 搭配购买
题目背景
Joe觉得云朵很美,决定去山上的商店买一些云朵。商店里有n朵云,云朵被编号为1,2,……,n,并且每朵云都有一个价值。但是商店老板跟他说,一些云朵要搭配来买才好,所以买一朵云则与这朵云有搭配的云都要买。但是Joe的钱有限,所以他希望尽量买的价值越多越好。
题目描述
无
输入格式
第1行,,,表示朵云,个搭配,Joe有的钱第行,每行,表示朵云的价钱和价值第行,每行,,表示买就必须买,同理,如果买就必须买。
输出格式
一行,表示可以获得的最大价值
输入输出样例 #1
输入 #1
1 | |
输出 #1
1 | |
说明/提示
的数据保证:
的数据保证:;;
的数据保证:;;
解答
分析
因为云朵需要搭配来买,所以可以用并查集将代价和价值捆绑到一起,接下来就转换成了一个01背包问题。
标程
1 | |
拓展并查集
有时,我们不只需要储存朋友关系,还需要储存敌人关系。我们知道并查集只能储存朋友,而不能储存敌人,所以我们可以用拓展并查集将敌人放进并查集中。
如何将敌人用朋友的方式标识出来呢?我们知道敌人的敌人就是朋友,所以不妨设一共有n个人,我们定义第a+n个点是第a个点的敌人,如果a与b是敌人,我们只需要让a和b+n成为朋友,a+n和b成为朋友,问题便迎刃而解。
接下来做一道模板题试一试吧!
例题3:盟友与宿敌
描述
在某个奇妙的魔法世界里,许多魔法师聚集在一起进行一场大型魔法竞技。他们之间有着复杂的关系:有些是盟友,有些则是宿敌。
规则如下:
1.我的盟友的盟友,也是我的盟友;
-
我的宿敌的宿敌,是我的盟友;
-
两个人只要是盟友,就认为他们属于同一魔法团队。
现在,给你若干魔法师之间的关系,请问:最多可以有多少个独立的魔法团队?
输入描述
第一行是一个整数,表示参赛的魔法师人数(从1编号到N)。
第二行是一个整数,表示关于魔法师的关系信息的条数。
以下M行,每行可能是F p q或E p q,
-
F 表示p和q是盟友,
-
E 表示p和q是宿敌。
输入数据保证不会产生信息的矛盾。
输出描述
输出文件只有一行,表示最大可能的魔法团队数。
解答
分析
使用拓展并查集进行模拟即可,因为要求找到魔法团队数,所以可以使用自动去重的set进行遍历根节点。
###标程
1 | |
接下来我们做两道比较难的拓展并查集题目。
例题4:P2024 [NOI2001] 食物链
P2024 [NOI2001] 食物链
题目描述
动物王国中有三类动物 ,这三类动物的食物链构成了有趣的环形。 吃 , 吃 , 吃 。
现有 个动物,以 编号。每个动物都是 中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这 个动物所构成的食物链关系进行描述:
-
第一种说法是
1 X Y,表示X和Y是同类。 -
第二种说法是
2 X Y,表示X吃Y。
此人对 个动物,用上述两种说法,一句接一句地说出 句话,这 句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
-
当前的话与前面的某些真的话冲突,就是假话;
-
当前的话中X或Y比N大,就是假话;
-
当前的话表示X吃X,就是假话。
你的任务是根据给定的 和 句话,输出假话的总数。
输入格式
第一行两个整数,,表示有 个动物, 句话。
第二行开始每行一句话。格式见题目描述与样例。
输出格式
一行,一个整数,表示假话的总数。
输入输出样例 #1
输入 #1
1 | |
输出 #1
1 | |
说明/提示
对于全部数据,,。
解答
分析
显然,由于有三种动物,所以拓展并查集需要开三倍大小,具体动物种类对问题没有影响。
对于判断假话环节,x或y比n大和x吃x很好解决,难点在于判断是否和前面冲突。
我们有这样一个关系图:
1 | |
如果他告诉我们x和y是同一种动物。但是实际上,x和y+n是一家的或者x和y+2n是一家的,这样两者是冲突的。
如果不冲突,直接合并x和y,x+n和y+n,x+2n和y+2n就可以了。
如果他告诉我们x能吃y,即x和吃y的人是一家的,即x和y+2n是一家的。但是若x和y是一家的,x和y+n(y的食物)是一家的,两者冲突。
如果不冲突,直接合并x和y+2n,x+n和y,x+2n和y+n就可以了。
标程
1 | |
例题5:P1525 [NOIP 2010 提高组] 关押罪犯
P1525 [NOIP 2010 提高组] 关押罪犯
题目背景
NOIP2010 提高组 T3
题目描述
S 城现有两座监狱,一共关押着 名罪犯,编号分别为 。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为 的冲突事件。
每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到 S 城 Z 市长那里。公务繁忙的 Z 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。
在详细考察了 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。
那么,应如何分配罪犯,才能使 Z 市长看到的那个冲突事件的影响力最小?这个最小值是多少?
输入格式
每行中两个数之间用一个空格隔开。第一行为两个正整数 ,分别表示罪犯的数目以及存在仇恨的罪犯对数。接下来的 行每行为三个正整数 ,表示 号和 号罪犯之间存在仇恨,其怨气值为 。数据保证 ,且每对罪犯组合只出现一次。
输出格式
共一行,为 Z 市长看到的那个冲突事件的影响力。如果本年内监狱中未发生任何冲突事件,请输出 0。
输入输出样例 #1
输入 #1
1 | |
输出 #1
1 | |
说明/提示
输入输出样例说明
罪犯之间的怨气值如下面左图所示,右图所示为罪犯的分配方法,市长看到的冲突事件影响力是 (由 号和 号罪犯引发)。其他任何分法都不会比这个分法更优。

数据范围
对于 的数据有 。
对于 的数据有 。
对于 的数据有 。
解答
分析
先将所有可能的冲突从大到小进行排序,依次解决,用并查集记录,知道有一个无法解决的就是答案。
标程
1 | |
最小生成树
最小生成树就是无向连通图中边权和最小的一棵树。
Kruskal 算法
Kruskal算法是一种非常简单的贪心算法,原理是为了让总边权最小,从小到大连接各边,同时使用并查集保证是一棵树。
具体实现需要先将各边排序,然后检测边是否能构成一颗树,如果可以就连接。
核心代码如下:
1 | |
来做一道模板题吧!
例题1:P3366 【模板】最小生成树
P3366 【模板】最小生成树
题目描述
如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz。
输入格式
第一行包含两个整数 ,表示该图共有 个结点和 条无向边。
接下来 行每行包含三个整数 ,表示有一条长度为 的无向边连接结点 。
输出格式
如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz。
输入输出样例 #1
输入 #1
1 | |
输出 #1
1 | |
说明/提示
数据规模:
对于 的数据,,。
对于 的数据,,。
对于 的数据,,。
对于 的数据:,,,。
样例解释:

所以最小生成树的总边权为 。
解答
分析
模板,没什么好说的。
标程
1 | |
当然,最小生成树经过略微变形,同样可以解决最小生成森林问题。
例题2:星际纺织工
描述
小光坐在飞船的舱室中,凝望通过舷窗看到的繁星点点的宇宙。
星际间流淌着五彩的星尘,就如同梦幻场景,小光想通过这些美丽的星尘编织几个星球间的星尘丝带。
宇宙中有N个星球,小光手上有M份星际地图,每份地图描绘了哪些星球之间可以用星尘编织。
现在小光要将所有星球通过星尘编织成K个星尘丝带,一个星尘丝带至少要连接一个星球,小光想知道他怎样编织,星尘的消耗最少。
输入描述
第一行有三个数字N,M,K
接下来M行,每行包含三个数值X,Y,L,表示星球X和星球Y可以用消耗L的星尘数量编织连接。
输出描述
对每组数据输出一行,仅包含一个整数 —— 最小的消耗代价。
如果无论如何都不能编织出K个星尘丝带,请输出 No Answer。
解答
分析
显然,题意等价于要求我们生成k个最小生成树。
而我们知道因为N个点,有N−K条边即可保证形成K个连通块,所以用N-K作为结束条件即可。
标程
1 | |
快速幂
极为基础的知识点,将取幂的任务按照指数的二进制表示来分割成更小的任务
标程如下:
1 | |
最大公约数
常用的求最大公约数的方法有两种:
1 | |
1 | |
如果要求最小公倍数也不难:
1 | |
1 | |
质数
检验质数
1 | |
对于找质数,常用的不是检验的方法,而是筛法,将合数筛掉,但值得一提的是检验法是在线算法,给它一个检验一个,而筛法是离线算法,一下把范围内的质数都找出来。
找质数:埃拉托斯特尼筛法
质数的倍数一定不是质数。
1 | |
找质数:欧拉筛法
不难发现,埃氏筛法中很多数会被重复筛掉,例如6会被2和3都筛一次,很浪费时间,欧拉筛法(又名线性筛法)便应运而生。
1 | |
做一道线性筛模板练手吧!
例题1:素数个数
描述
求1,2,…,N中素数的个数
输入描述
一行一个整数N
输出描述
一行一个整数,表示素数的个数。
解答
分析
欧拉筛得出范围内所有质数,然后用size得出prime大小即可。
标程
1 | |
经过思考,我们不难发现,由于线性筛不会重复筛除,所以求质数的同时也得到了每个数的最小质因子。
例题2:B4272 [蓝桥杯青少年组省赛 2023] 质因数的个数
B4272 [蓝桥杯青少年组省赛 2023] 质因数的个数
题目背景
-
因数:又称为约数,如果整数 除以整数 的商正好是整数而没有余数,我们就说 是 的因数。
-
质数:又称为素数,一个大于 的自然数,除了 和它自身外,不能被其他自然数整除的数叫做质数。 是最小的质数。
-
质因数:如果一个数 的因数 同时也是质数,那么 就是 的一个质因数,例如:, 就是 的质因数;, 和 就是 的质因数。
题目描述
给定两个正整数 和 ,统计 到 之间(含 和 )每个数所包含的质因数的个数,输出其中最大的个数。
例如:当 , 到 之间:
-
的质因数是 ,共有 个;
-
的质因数是 ,共有 个;
-
的质因数是 ,共有 个;
-
的质因数是 ,共有 个;
-
的质因数是 ,共有 个;
到 之间的数中质因数最多的是 ,质因数有 个,故输出 。
输入格式
输入两个正整数 和 ,两个正整数之间用一个空格隔开。
输出格式
输出一个整数,表示质因数个数中的最大值。
输入输出样例 #1
输入 #1
1 | |
输出 #1
1 | |
解答
分析
首先很显然可以通过线性筛筛出每个数的最小质因数,又很显然,假设i的最小质因数是p,那么i的总质因数个数等于i/p的质因数个数+1!据此模拟即可。
标程
1 | |
裴蜀定理
裴蜀定理
很好理解,若有一个方程ax+by=d,有且只有当d为gcd(a,b)时方程才有整数解,而且可以推导出a与b互质当且仅当存在x,y满足ax+by=1.
很有趣的,裴蜀定理可以进行推广,存在整数 ,使得 成立.
现在来做一道简单的题吧!
例题1:裴蜀定理
描述
给定一个包含n个元素的整数序列A,记作 。
求另一个包含n个元素的待定整数序列X,记$S=∑^n_{i=1} A_i×X_i,使得S>0且S尽可能的小。
输入描述
第一行一个整数n,表示序列元素个数。
第二行n个整数,表示序列A。
输出描述
一行一个整数,表示
S>0 的前提下S的最小值。
解答
分析
就是推广裴蜀定理,求出总的gcd即可。
标程
1 | |
拓展欧几里得
那么面对ax+by=gcd(a,b)时我们怎么求x和y呢?
此时我们可以使用拓展欧几里得算法!
由欧几里得定理可知:
所以
又因为
所以
因为 ,所以
将不断代入递归求解直至gcd=0,递归x=1,y=0 回去求解.
具体实现如下:
1 | |
??
??
??
??

