夏令营讲课内容整理 Day 2.

本日主要内容是并查集和堆。

  1. 并查集

并查集是一种树型的数据结构,通常用来处理不同集合间的元素之间的合并与查找问题。一个并查集支持三个基本功能:合并、查找和判断。举一个通俗的例子,我和lhz认识,lhz和hzc认识,那么也就可以断定我和hzc认识。

依照并查集的思想,我们把所有要待处理的元素a1,a2,a3....an这n个元素都看作是一个单独的集合,初始状态每个集合都只有一个元素。我们就可以把并查集的合并操作理解为集合之间的取并集操作。

作为一个树形结构,在一个由许多这样的集合构成的森林中,每个集合都应该有它自己的「代表元素」,即能够代表一整个集合的所有元素的元素。可以这样理解,在一个地方存在着许多黑恶势力,而每个黑恶势力都有一个自己的头目,这个头目就是集合里的「代表元素」。对于并查集,每个集合选择哪个元素作为代表元素不是我们要关心的问题,但是我们要保证这个代表元素要在集合不发生改变的状态下是不变的,换句话说,不能随便换头目。

那么这应该怎么操作呢?

对于一棵树,我们通常使用一个father数组记录某点的父亲节点,即用father[i]表示第i号点的父亲节点是谁。但是对于并查集,这个father数组则就是用来记录第i个点所在的集合的「代表元素」。

则初始化一个并查集的代码就明确了。

for (int i=1;i<=n;i++)
    father[i] = i;

下面介绍查找代表元素的find操作。find的作用是查找一个节点x所在集合的代表元素。

int find(int x){
    if (father[x] == x)
        return x;
    else
    return find(father[x]);
}   

它使用了递归。可以想象一个暴力爬树的过程,如果我现在在i号节点,我为了要找到我们这个集合的代表元素,我肯定要沿着我的father向上爬,直到我找到一个元素,它的father是它本身,那么这就一定是那个黑恶势力的头目了。

时间复杂度O(h),h代表这个点距离代表元素的高度。

*路径压缩

我们考虑极端情况。如果有一个集合的某个链非常非常长,而我们要找到这个集合最下边的代表元素的话,是需要O(h)时间的,这个h有可能会非常大,如果再这样使用暴力爬树的方法就会吃到一个TLE。

不怕,我们有路径压缩!

路径压缩的思想非常简单,我在find的时候不是要不断地找祖先吗,那如果所有节点都几乎直接插在代表元素节点不远处,查找的次数不就大大变少了?说的再通俗一点,就是把一条路上的节点的father全部更新成真正的代表元素,而不是合并之前的代表元素,这就相当于是直接把这个地方插到代表元素上,所以就大大减少的查找的递归次数。

还是听不懂?Shut up and take my code!

int find(int x){
    if (father[x] == x)
    return father[x];
    father[x] = find(father[x]);//如果当前节点的father并不是代表元素,那就递归地更新老祖宗
    return father[x];//返回老祖宗
}

这个函数的时间复杂度是O(α(n)),α(n)代表反阿克曼函数,反阿克曼函数时一个增长非常缓慢的函数,通常来说,反阿克曼函数的最大值不会超过4,所以路径压缩的find完全可以看作是一个O(1)的常数操作。

合并集合:只需要把一个头目变成另一个头目的下属就可以了。

void merge(int x,int y){
    x = find(x);
    y = find(y);
    father[y] = x;
}

判断两个元素是不是在同一集合,只需要问问他们的头目是谁就知道了。

bool check(int x,int y){
    x = find(x);
    y = find(y);
    if (x==y)
        return true;
    else
        return false;
}

很简单吧,是不是?

并查集的应用:kruskal算法求图上的最小生成树。

这里我先卖个关子,等我写到Day3 图论时再去详细介绍这个算法,它并不难理解。

例题:

T1:程序自动分析

luoguP1955.

离散化+并查集可做。所谓离散化就是把输入时相同的内容都去掉,可以用map,hash或者unique函数等等操作……我应该会在后面的一篇随笔上详细的介绍离散化操作,这里只是给一个粗略的思想。对于这个题我们可以用并查集维护所有数之间的相等关系。先处理所有的等式,将等式两边的两个数所在的集合合并在一起。然后我们检查所有的不等式。如果某个不等式两边的数字在一个集合中(被要求相等),则输出NO。不存在这样的情况则输出YES。

用两个并查集分别记录相等和不等关系好像也是可以的。

此外,这道题在Day1上午的模拟赛上也出现了。

T2:Connect

(我在luogu上找不到这题)

给定一个点数为??、边数为??的无向图,请编写一个程序支持以下两种操作:

1、?? ?? ??,在原图中删除连接顶点??和顶点??的边。

2、?? ?? ??,询问??顶点和??顶点是否联通。

??, ?? ≤500,000

看到Q x y这样的操作很显然想到是使用并查集。但是路径压缩之后的就并查集并不支持删除边的操作了,这怎么办呢?

倒着处理就好了嘛! 我们可以建立一个存储“删除”的并查集,既然D x y是把xy这条边删除,那我们就可以把它在“删除”并查集里合并。询问也变成了询问两个点是不是在同一集合中,这样就可以了。

但这样处理需要先存起来再进行,这样的操作我们称之为「离线操作」,就好像是从网上把数据下载下来再从本地进行操作那样。反之我们有「在线操作」,即边读边处理,边读边处理的在线操作基本上不需要数组等开销空间较多的数据结构,所以很多时候在线处理会省一点空间。

具体到某个体是要在线处理还是离线处理,这个不一定。

T3:Cube Stack

待续

T4:食物链

luoguP2024

啊这题太难了QAQ我不会做

待续

2. 堆

堆也是一种特殊的树形数据结构。它的本质是一棵完全二叉树(有时也是一个满二叉树)。

堆有大根堆和小根堆两种形态。大根堆即是对于任意一点,它的值比任何一个下面的点都要大,小根堆则就是值要比任何一个下面的点都要小。

所以:大根堆的根节点一定权值最大,小根堆的根节点一定权值最小

实现方式:手写 or STL的priority_queue

(priority_queue其实叫优先队列,虽然挂着一个队列的名字,但它满足堆的所有性质,我们可以用它来代替手写堆,但在不开-O2优化下速度相对手写堆慢一些)

(具体代码实现请参照一本通 or 课件)

维护(即调整)一个堆的具体思想就是,我每次要删除一个最大/最小的元素,那就要把根节点删除,然后把最后一个点作为新的根节点,但这个点有可能不满足堆的性质,那么我们就把这个不合法的点与它的儿子交换,不断地交换直到成为叶节点或者比儿子小/大。

(如果比两个儿子都小/大,就与最大/小的儿子交换。

维护的必要性:只有所有子树是一个合法的堆,整棵树才是一个合法的堆。

加入新节点与维护操作类似。如果现在堆中共有tot个节点,我们把新的节点放在位置tot+1上。我们不停地比较当前点和父亲,如果当前点比父亲大/小,则交换,直到交换到根或者不再比父亲大/小为止。

应用:堆排序

手写堆的做法:先将整个序列维护成一个堆,然后提取出根节点,输出,并删除。将最后一个点放到根节点的位置,再次维护,要保证维护后,仍然满足堆的性质。重复这一步,直到所有的元素都被删除。

STL的做法:push push push push push 输出top pop 输出top pop……

例题:

T1: 合并果子

luoguP1090

模板题。考虑贪心思想,我们每次取果子合并时应该要取两堆最小的合并,这样不会再有其他合并操作比这个更优。证明从略。

这样我们把序列维护成一个小根堆,每次取前两个节点,将值相加后把结果加入堆,一直这样做直到堆只剩一个节点 ,这个节点的值就是答案。

这是一个叫蛤(哈)夫曼树的经典模型。它的定义是:给定n个权值作叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,别名哈夫曼树。

时间: 2024-10-12 08:30:49

夏令营讲课内容整理 Day 2.的相关文章

夏令营讲课内容整理Day 0.

今年没有发纸质讲义是最气的.还好我留了点课件. 第一次用这个估计也不怎么会用,但尝试一下新事物总是好的. 前四天gty哥哥讲的内容和去年差不多,后三天zhn大佬讲的内容有点难,努力去理解吧. 毕竟知识还是需要消化的. 这里我只整理知识点,每天上午评测的题目我会单独处理. 嗯大概就是这样了. 写完后我就会考虑发到博客园里.

夏令营讲课内容整理 Day 3.

本日主要内容是树与图. 1.树 树的性质 树的遍历 树的LCA 树上前缀和 树的基本性质: 对于一棵有n个节点的树,必定有n-1条边.任意两个点之间的路径是唯一确定的. 回到题目上,如果题目读入的是树上所有的边,则我们应该想到: 每个点的父亲是谁 每个点的深度 每个点距离根节点的距离 其他的附加信息(例如:子树和,子树最大值..) 遍历整个树的代码如下: 1 void dfs(int now) 2 { 3 deep[now]=deep[fa[now]]+1; 4 sum[now]=value[n

夏令营讲课内容整理 Day 5.

DP专场.. 动态规划是运筹学的一个分支, 求解决策过程最优化的数学方法. 我们一般把动态规划简称为DP(Dynamic Programming) 1.动态规划的背包问题 有一个容量为m的背包,有n个物品,每一个物品i的重量为w[i],价值为v[i]. 要求选择一些物品放入背包中,每种物品只能最多使用一次,使得在不超重的情况下让背包中所有物品价值总和最大. 正常向解法:设状态数组f[i][j]为把前i个物品放入一个容量为j的背包中所能获得的最大价值(以下同设),则状态转移方程为: f[i][j]

夏令营讲课内容整理 Day 4.

本日主要内容就是搜索(打暴力 搜索可以说是OIer必会的算法,同时也是OI系列赛事常考的算法之一. 有很多的题目都可以通过暴力搜索拿到部分分,而在暴力搜索的基础上再加一些剪枝优化, 就有可能会拿到更多的分数. 有句话说的好嘛,骗分过样例,暴力出奇迹. 真的可以出奇迹的,只要你用得好. 1.搜索的概念 在一个给定的空间内,运用一定的查找(遍历)方式,直到找到目标解(状态)的过程,我们称之为搜索. 搜素是尝试性的,搜索是无脑的,搜索是朴素的,搜索在很多时候是显然的,搜索应该总是暴力的.但搜索也是很常

夏令营讲课内容整理Day 1.

主要内容是栈和队列. 1.  栈 运算受到限制的线性表.只允许从一端进行插入和删除等操作.这一端便是栈顶,另一端便是栈底. 其实可以把栈想象层任何有底无盖的柱状的容器...毕竟栈满足后进先出的特性.计算机当中调用函数时,中间结果便会保存到「系统栈」中.递归过程也需要栈的协助 . 实现:STL or 手写(请参照一本通 or 课件) 一般操作:判断栈空/满.入栈.出栈,判断栈的大小(请参照一本通 or 课件) 1.1 单调栈 顾名思义,保证内部元素单调(单增或单减)的栈.我们只要在插入新元素的时候

夏令营讲课内容整理 Day 6 Part 1.

Day6讲了三个大部分的内容. 1.STL 2.初等数论 3.倍增 Part1主要与STL有关. 1.概述 STL的英文全名叫Standard Template Library,翻译成中文就叫标准模板库. 它有点类似于一个大型的工具箱,里面包含许多实用工具,可以拿过来直接用而大部分情况下无需去深入探究其内部原理. 不知道从什么时候开始,CCF不再限制选手使用STL,所以在OI赛事中STL被广泛应用. 它分为六个大部分: 1)容器 containers 2)迭代器 iterators 3)空间配置

夏令营讲课内容整理 Day 6 Part 2.

Day 6的第二部分,数论 数论是纯粹数学的分支之一,主要研究整数的性质 1.一些符号: a mod b 代表a除以b得到的余数 a|b a是b的约数 floor(x) 代表x的下取整,即小于等于x的最大整数,也可以认为是直接舍去小数部分 (这个应该是一个符号,但我不知道怎么打出来..下面那个ceil也是) ceil(x) 代表x的上取整,即大于等于x的最小整数,也可以认为是直接舍去小数部分再+1. gcd(a,b) 表示a与b的最大公约数 lcm(a,b) 表示a与b的最小公倍数 累加符号∑

Google C++ 风格指南内容整理

之前一直没有全面的看过Google C++风格指南,现在很多公司进行C++开发都要求按照Google C++风格.在这个网站 http://zh-google-styleguide.readthedocs.org/en/latest/contents/  有人已经把其翻译成中文.为了便于以后查看,下面的内容完全是来自于这个网站,只是把多个网页的内容整理放在了一起. 1.      头文件: 通常每一个.cc文件都有一个对应的.h文件.也有一些常见例外,如单元测试代码和只包含main()函数的.c

网页格式化排版代码,专用信息采集后的内容整理

public static string ClearHtml(string content) { Regex regex = new Regex(""); //首先把p标签的属性去掉,只留<p> regex = new Regex(@"<p.*?>", RegexOptions.IgnoreCase | RegexOptions.Singleline); content = regex.Replace(content, "<p