郑州集训DAY3笔记

来源:互联网 发布:c语言编程器下载 编辑:程序博客网 时间:2024/04/18 16:45

day1难度测试

成绩:

题目 成绩 评价 T1 100 感动。。总算上了一百分 T2 10 本来以为能骗到40??? T3 0 真的不会做

数据结构

字符串Hash

字符串Hash:一种从字符串到整数的映射
通过这样的映射,把比较两字符串是否相同转化为两整数是否相同
若比较发现两字符串hash值相等,我们认为两字符串很大可能是相同的
另一方面,若 hash 值不等,则两字符串一定不同
比较字符串 O(L),比较整数 O(1)

BKDR-Hash

竞赛中常用的 hash 策略
把字符串视为一个 base 进制的大整数,对某个质数 P 取模得到 hash 值
sum_i = (sum_{i-1} *base + str_i) mod P
base 可以取 31、131、13131 等,需要满足 base > |字符集|
P 取 long long 范围内一个质数,注意溢出问题
使用 unsigned long long 自然溢出可以视为对 2^64 取模
但是可能被卡(对任意base)
害怕 Hash 被卡的同学,也可以选择双 hash(常数翻倍

常用技巧
—-给定字符串 S,预处理出它的前缀 Hash 函数;同时计算好 mod P 意义下 base 的幂次表

sum[i] = (sum[i - 1] * base + str[i]) % Ppw[i] = (pw[i - 1] * base) % P

基础应用:
提取一段子串的 hash 值
合并两个串的 hash 值
O(\log n) 计算两个子串的 lcp 和字典序大小

Trie 树

又称字母树,可以用来维护字符串集合
优化思想是,利用字符串的公共前缀来减少查询时间,最大限度地减少无意义的比较
这里写图片描述
这是一颗Trie树。有根树,每条边上存有一个字符,从根到每个叶子的路径上经过的字符写下来,对应了一个字符串。
支持的操作有:插入、查找、删除。

并查集

可以参考:并查集入门by柴犬首相
原理
对每个集合,建立一个有根树的结构,令树的根为整个集合的“代表”,想知道两个元素是否在同一集合,只需比较它们的代表,合并时,将一棵树接到另一棵下边即可。
优化策略

  • 路径压缩
  • 按秩合并
  • 可以证明,使用这两种优化的并查集复杂度为 O(α(n))
  • 绝大多数情况这个值不大于 5,可以认为是线性的

应用
最小生成树的 Kruskal 算法
Tarjan离线LCA算法。
带权并查集
在一些应用中,可以在每个点上额外维护一些信息,表示“它与父亲”之间的关系,进而尝试推算集合中任意两个元素之间的关系。
例题——食物链
题目地址luogu食物链
题解地址:食物链题解by柴犬首相
只按秩合并
只按秩合并的并查集,可以在合并的时候一定程度上保留元素合并在一起的 “过程”

优先队列

支持这样几种操作的数据结构:
插入一个优先级为 key 的元素
询问优先级最高的元素
删除优先级最高的 / 任意一个元素
升高一个元素的优先级值
优先队列一般使用堆来实现
最经典的堆即为大名鼎鼎的二叉堆

二叉堆

二叉堆是一个完全二叉树结构,并且它具有堆性质:,每个点的优先级高于它的两个孩子(如果有)。可以用一个数字来存储二叉堆,避免指针:
1 是根结点,对于 x,它的左右孩子分别是 2x 和 2x+1,容易验证 N 个点的二叉堆,它用到的数组即为 1 ~ N,给定一个大小为 N 的数组,我们可以 O(N) 的建堆。
调整
随着操作的进行,二叉堆的“堆性质”可能会遭到破坏,为此我们定义两种调整操作,来维护二叉堆的堆性质保持不变
向上调整
当一个点的优先级升高时,我们需要向上调整
比较它和它的父亲的优先级,它的优先级高就与父亲交换位置并递归进行
向下调整
当一个点的优先级降低时,我们需要向下调整
比较它和它左右儿子中优先级较高的那个,它的优先级低就与儿子交换并递归下去
容易验证两种操作的复杂度均为 O(\log N)
操作
插入:插入一个叶子,然后向上调整
询问:返回 a[1]
删除根:令 a[1] = a[N],然后向下调整
左偏树
也是一种优秀的堆,并且是支持合并的(可并堆),比较简单,容易实现。但是我们不讲。
应用
- Dijkstra 算法和 Prim 算法的优化
- 哈夫曼编码
- 一些奇怪的应用

线段树入门

可以参考:线段树入门by柴犬首相
线段树是一种二叉搜索树,一般可以用来维护序列的子区间
这里写图片描述
结构
对一个长度为 n 的序列建线段树,根结点即表示 [1, n]
对于一个表示 [l, r] 的节点:,若 l = r,则它是叶子。否则,令 m = (l + r) / 2,它有左右两个孩子,分别记为:[l, m] 和 [m + 1, r],不难验证,这样一个线段树中有 2N - 1 个节点,并且树的深度是 O(\log N) 级别。
原理
线段树的优化思想:
根据问题的要求,用每个节点维护它对应的子区间中、可以高效合并的相关信息,在动态的序列问题中,对于修改操作没有动过的部分。我们可以考虑把这些地方的求解的结果保存并复用,从而达到优化程序效率、降低复杂度的目的。

树状数组

一种支持单点修改和查询前缀和的数据结构,复杂度为 O(\log N),但是常数很小。
原理
定义 lowbit(x),表示将 x 写成二进制后,只保留二进制下最低一个 1 对应的整数
例:lowbit(1001100) = 100, lowbit(1000) = 1000
十进制:lowbit(76)=4, lowbit(8) = 8
对一个数组 a[],我们构造数组 c[],其中
c[i] = sum (. a[i - lowbit(i) + 1 … i] )
巧妙的事情来了:
我们查询 a[] 的前缀和只需要访问 c 中 log N 个节点
修改 a[] 中任意一个元素的值,只需要同时修改 c 中的 log N 个节点
于是可以在 O(\log N) 的时间内支持单点修改、前缀和查询

树状数组 vs 线段树

复杂度均为 O(\log N)
优点
树状数组常数小、跑得快;线段树常数较大,跑得稍慢
树状数组相比线段树好实现
树状数组可以比较简单地推广到二维
不足
树状数组的功能是线段树的子集

二维树状数组

对二维数组 a[][],维护 c[][] 表示
c[i][j] = sum( a [ i-lowbit(i)+1 … i] [ j-lowbit(j)+1 … j] )
可以支持二维数组的单点修改、查询前缀矩形的和
复杂度 O(\log^2 N)

区间加+区间查询

想支持对数组 a[] 的区间加整数、区间查询和,考虑 a[] 的差分数组 b[i] = a[i] - a[i - 1],区间加对 b[] 只修改两个位置!同时,为了支持查询 a[] 的前缀和,经过一番推导,发现只需维护 b[i] 和 i * b[i] 的前缀和即可,复杂度 O(\log N)

总结

今天讲了数据结构,但是吕欣大佬晚上赶车回家过中秋节,。。我们树状数组实在是没有怎么讲,要再看。