算法进阶课——7.1 启发式合并、Manacher算法

image-20210807031457384

7.1 启发式合并、Manacher算法


启发式合并


启发式合并类似于并查集的按秩合并.

例如,一开始有 n n n 个数,每个数在仅包含自己的单独的集合当中,每次将某个集合内的所有元素,合并到另外一个集合当中,最终要将所有数都合并到一个集合当中.

如果暴力地去实现的话,将包含 a a a 个元素的集合,合并到另外一个集合当中,则其时间复杂度为 O ( a × o p ) O(a \times op) O(a×op),其中 o p op op 为每个元素合并到另一个集合的时间复杂度.

其中 a a a 可以取为 1 ∼ n − 1 1 \sim n-1 1n1,故暴力实现的总时间复杂度为 O ( n 2 × o p ) O(n^2 \times op) O(n2×op),这是一个比较慢的时间复杂度.

  • ∑ i = 1 n − 1 i = ( 1 + n − 1 ) × ( n − 1 ) 2 = n 2 − n 2 ≈ n 2 \sum_{i=1}^{n-1}i=\frac{(1+n-1)\times(n-1)}{2}=\frac{n^2-n}{2}\approx n^2 i=1n1i=2(1+n1)×(n1)=2n2nn2.

可以考虑在合并时做一个优化,因为每一次合并实际上是将两个集合合并成一个集合,可以将第一个集合合并到第二个集合当中,也可以将第二个集合合并到第一个集合当中.

所以每次合并的时候,加一个启发式的操作,即将元素少的集合合并到元素多的集合当中,这样就可以把总时间复杂度降到 O ( n log ⁡ n × o p ) O(n\log n \times op) O(nlogn×op).

  • 并查集的按秩合并是依据树的高度来合并的,而启发式合并是按照元素个数来合并的,但思想是类似的.

证明

如果直接用一个集合有 k k k 个元素,合并一次要 O ( k × o p ) O(k \times op) O(k×op) 的时间复杂度的方式去算,是比较困难的.

可以考虑换一种方式来算,考虑对于每个元素来说,它最终对计算量的贡献是多少(即它被合并多少次)?

  • 例如将第一个集合与第二个集合合并,则在第一个集合内的元素 u u u 被合并了一次,随后将第二个集合与第三个集合合并,则元素 u u u 又被合并了一次,所以每个元素最终被合并的次数取决于每次它所在集合被合并的次数.

  • 那么每个元素所在集合最多会被合并多少次呢?由于每次都是将元素较少的集合合并到元素较多的集合当中,如果元素较少的集合内有 x x x 个元素的话,那么合并之后的集合内的元素一定 ⩾ 2 x \geqslant 2x 2x.

  • 所以对于某一个元素 u u u 而言,它所在的集合每合并一次,它所在集合的元素数量就至少会 × 2 \times 2 ×2.

  • 一共有 n n n 个元素,所以对于每个元素而言,最多只会合并 log ⁡ n \log n logn 次,所以最多总共合并的次数为 n log ⁡ n n\log n nlogn 次.

证毕

AcWing 2154. 梦幻布丁

原题链接

一天,小徐的好友邀请他去吃布丁,于是小徐高高兴兴的来到好友家。

哇,这么多五彩缤纷的布丁!

好友说:“在我们开吃前先玩会儿游戏吧。”

于是他将布丁摆成一行,接着说:“我可以把某种颜色的布丁全部变成另一种颜色,我还会在某些时刻问你当前一共有多少段颜色。例如:颜色分别为 1 , 2 , 2 , 1 1,2,2,1 1,2,2,1 的四个布丁一共有 3 3 3 段颜色。”

输入格式

第一行包含整数 n n n m m m,分别表示布丁的个数和好友的操作次数。

第二行包含 n n n 个空格隔开的整数 A 1 , A 2 , … , A n A_1,A_2,…,A_n A1,A2,,An,其中 A i A_i Ai 表示第 i i i 个布丁的颜色。

从第三行起的 m m m 行,依次描述 m m m 个操作。

对每个操作,若第一个数是 1 1 1,则表示好友要改变颜色,这时后跟两个整数 x x x y y y(可能相等),表示执行该操作后所有颜色为 x x x 的布丁被变成颜色 y y y

若第一个数是 2 2 2,则表示好友要询问目前有多少段颜色,这时应该输出一个整数回答。

输出格式

对于每个询问,在一行中输出一个整数作为回答。

数据范围

0 < n , m < 100001 0<n,m<100001 0<n,m<100001,
0 < A i , x , y < 1 0 6 0<A_i,x,y<10^6 0<Ai,x,y<106

输入样例:

4 3
1 2 2 1
2
1 2 1
2

输出样例:

3
1

时/空限制: 1s / 64MB
来源: HNOI2009
算法标签: 启发式合并 链表 Treap 线段树

yxc’s Solution

  • 如何统计不同的颜色段数?

    • 初始的时候,可以直接扫描一遍.
  • 每次在合并的时候,考虑如何维护段数?

    • 第一种操作,是将某种颜色的布丁全都变成另外一种颜色,其实本质上就是将这两种颜色的布丁合并成同一类(也就是说,不论是颜色 x x x 变为颜色 y y y,还是颜色 y y y 变为颜色 x x x,对答案的影响没有区别);

    • 显然,将两种颜色的布丁合并不会让颜色段数增加,考虑两个相邻的布丁,只有当它们原本颜色相同而经过操作后颜色不同,才会使得颜色段数增加;但这是不可能的,因为每次操作都是将一种颜色全部变成另外一种颜色;

    • 考虑什么情况会让颜色段数减少,只有当两个相邻的布丁,在原本颜色不同的情况下,因为操作使得它们的颜色变得相同,才能使颜色段数减少 1 1 1

      举例,对于颜色为 x x x 的布丁,对它进行操作时,检查其左右两边的布丁是否为其要变成的颜色 y y y.

      如果左边的布丁颜色为 y y y 的话,则颜色段数 − 1 -1 1;如果右边的布丁颜色为 y y y 的话,则颜色段数 − 1 -1 1.

    • 每次想要将颜色为 x x x 的布丁全部变为颜色为 y y y 的布丁,只需要遍历颜色为 x x x 的布丁,判断其左右两边的布丁颜色即可;

    • 如果直接暴力合并的话,时间复杂度最坏是 O ( n 2 ) O(n^2) O(n2) 的,所以可以使用启发式合并使时间复杂度降到 O ( n log ⁡ n ) O(n\log n) O(nlogn).

  • 如何去存储信息?

    • 先开一个数组来表示所有颜色,来表示某种颜色对应的颜色编号,一开始,所有颜色的对应的颜色编号就是其本身;

    • 每种颜色对应的编号会拉一个单链表出来,用来存储所有这种颜色的布丁的位置

    • 每次将颜色为 x x x 的布丁合并到颜色为 y y y 的布丁时(这里假设颜色为 x x x 的布丁数量比颜色为 y y y 的布丁数量小),需要将颜色为 x x x 的布丁的链表接到颜色为 y y y 的布丁的链表上.

      image-20210807031457384
      颜色数组图示

      注意,如果这里的操作与输入数据中的 x x x

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
http://www.400gb.com/u/256394 译者序 前言 致谢 第1章 绪论  1.1 评估算法  1.2 修改算法   1.2.1 主要的优化:I/O   1.2.2 主要的优化:函数调用  1.3 资源和参考资料 第2章 基本数据结构  2.1 链表   2.1.1 双向链表   2.1.2 链表的其他特征  2.2 栈和队列   2.2.1 栈的特征   2.2.2 队列的特征 第3章 散列  3.1 散列的概念  3.2 散列函数  3.3 冲突解决方法   3.3.1 线性再散列法   3.3.2 非线性再散列法   3.3.3 外部拉链法  3.4 性能问题  3.5 资源和参考资料 第4章 查找  4.1 查找的特征   4.1.1 准备时间   4.1.2 运行时间     4.1.3 回溯的需要  4.2 蛮力查找  4.3 Boyer Moore查找   4.3.1 启发式方法#1:跳过字符   4.3.2 启发式方法#2:重复模式  4.4 多字符串查找  4.5 用于正则表达式的字符串查找:grep  4.6 近似字符串匹配技术  4.7 语音比较:Soundex算法  4.8 Metaphone:现代的Soundex  4.9 选择技术  4.10 资源和参考资料   4.10.1 通用参考资料   4.10.2 Boyer Moore   4.10.3 多字符串查找   4.10.4 正则表达式查找   4.10.5 近似字符串匹配   4.10.6 Soundex算法和Metaphone算法 第5章 排序  5.1 排序的基本特征   5.1.1 稳定性   5.1.2 对哨兵的需求   5.1.3 对链表进行排序的能力   5.1.4 输入的阶的相关性   5.1.5 对额外存储空间的需求   5.1.6 内部排序技术与外部排序技术  5.2 排序模型   5.2.1 冒泡排序   5.2.2 插入排序   5.2.3 希尔排序   5.2.4 快速排序   5.2.5 堆排序  5.3 对链表进行插入排序  5.4 对链表进行快速排序  5.5 对多个键进行排序——不稳定排序的修正方法  5.6 网络排序  5.7 小结:选择一种排序算法  5.8 资源和参考资料 第6章 树  6.1 二叉树   6.1.1 树查找   6.1.2 节点插入   6.1.3 节点删除   6.1.4 二叉查找树的性能   6.1.5 AVL树  6.2 红黑树  6.3 伸展树  6.4 B树   6.4.1 保持B树平衡   6.4.2 实现B树算法   6.4.3 B树实现的代码  6.5 可以看见森林吗  6.6 资源和参考资料 第7章 日期和时间  7.1 日期例程的库  7.2 时间例程  7.3 用于日期和时间数据的格式  7.4 最后的提醒  7.5 资源和参考资料 第8章 任意精度的算术  8.1 构建计算器8.2表示数字  8.3 计算  8.4 加法  8.5 减法  8.6 乘法  8.7 除法  8.8 关于计算器要注意的最后几点  8.9 用于计算平方根的牛顿算法  8.10 分期付款表  8.11 资源和参考资料 第9章 数据压缩  9.1 行程编码  9.2 霍夫曼压缩   9.2.1 代码   9.2.2 其他问题  9.3 滑动窗口压缩  9.4 基于字典的压缩(LZW)   9.4.1 LZW算法的伪代码   9.4.2 LZW压缩的实现   9.4.3 填满字典  9.5 使用哪种压缩方法  9.6 资源和参考资料 第10章 数据完整性和验证  10.1 简单的校验和  10.2 加权校验和  10.3 循环冗余校验   10.3.1 CRC CCITT   10.3.2 CRC 16   10.3.3 CRC 32   10.4 资源和参考资料

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值