我阅读的算法书籍是李煜东的《算法竞赛进阶指南》。翻开目录,我第一眼关注到的就是数据结构进阶这一类,毕竟上学期刚学数据结构,相信这一块是能够最快接受的知识点。
第一节讲的是并查集,这还是比较简单。并查集就分为两步,第一步是查询一个元素属于哪个集合,第二步是把两个小集合合并成一个大集合。那么合并集合的依据是什么呢?如果每个集合都选出一个“代表”元素,即该集合内的每一个元素查询后的结果是这个“代表”。显然每一个“代表”的查询结果是自己,而合并,就是让其中一个集合的“代表”成为另一个集合的“代表”的“代表”,这样再更新其他成员的“代表”,这样合并就完成了。用一句通俗的话讲就是“我大哥的大哥就是我的大哥”,只是这样原来的“大哥”变成了和自己平级的存在了。就和它的名字一样,就是查询两个个集合的“代表”,再通过改变“代表”的指向关系,使两个集合就可以合成为一个。
实际上,我们在查询过程中只关心“代表”是什么,因此我们可以在查询操作的时候将访问过的每个点都指向“代表”,这样的方法叫做路径压缩,单次操作复杂度为O(logN)。
那么并查集可以拿来解决哪些问题呢?比如说需要你去区分阵营,区分敌人和朋友的时候,就可以用并查集,毕竟我朋友的朋友也是我的朋友。还有就是可以用并查集来判断无向图是否有回路,通过无向图的一条条路将一个个单独的点添加进集合的时候,如果发现某一条路上的两个点的“代表”是同一个,那么这个无向图就是有回路。
后面一讲是树状数组,它的功能主要就是查询前缀和,算法的复杂度更是只有O(logN)。这里我看了好久不知道为什么要用x&(-x)。直到理解了后面的图。如果说原数组为An,对应的树状数组是Cn。通过后面的图我们可以发现:
C[1] = A[1];
C[2] = A[1] + A[2];
C[3] = A[3];
C[4] = A[1] + A[2] + A[3] + A[4];
C[5] = A[5];
C[6] = A[5] + A[6];
C[7] = A[7];
C[8] = A[1] + A[2] + A[3] + A[4] + A[5] + A[6] + A[7] + A[8];
再C[i] = A[i - 2k+1] + A[i - 2k+2] + … + A[i];k为i的二进制中从最低位到高位连续0的长度,而要找出这个0的长度,就要用到i&(-i)。当然直接用C[i]不一定是i的前缀和,所以需要i-=i&(-i)后再加上C[i]补齐前面漏下的项。比如说i=5,C[5]=A[5];i-=i&(-i)后i=4,C[4] = A[1] + A[2] + A[3] + A[4];i-=i&(-i)后i=0,结束,这时ans=C[5]+C[4]=A[1]+A[2]+A[3]+A[4]+A[5].然后查询区间[l,r]中所有数的和,只需要r的前缀和减去(l-1)的前缀和。
树状数组还有一个功能就是单点更新,实际操作就是:如果a[i]更新了,通过i+=i&(-i)将C数组中所有包含a[i]的元素进行更新。
07-29
09-11
09-11