判断一些树是否同构的时,我们常常把这些树转成哈希值储存起来,以降低复杂度。
树哈希是很灵活的,可以设计出各种各样的哈希方式;但是如果随意设计,很有可能是错误的,可能被卡。以下介绍一类容易实现且不易被卡的方法。
这类方法需要一个多重集的哈希函数。以某个结点为根的子树的哈希值,就是以它的所有儿子为根的子树的哈希值构成的多重集的哈希值,即:
其中
以代码中使用的哈希函数为例:
其中
这种哈希十分好写。如果需要换根,第二次 DP 时只需把子树哈希减掉即可。
这是一道模板题。不用多说,以
??? "参考代码"
cpp --8<-- "docs/graph/code/tree-hash/tree-hash_1.cpp"
这道题所说的同构是指无根树的,而上面所介绍的方法是针对有根树的。因此只有当根一样时,同构的两棵无根树哈希值才相同。由于数据范围较小,我们可以暴力求出以每个点为根时的哈希值,排序后比较。
如果数据范围较大,我们也可以使用换根 DP,遍历树两遍,求出以每个点为根时的哈希值。我们还可以利用上面的多重集哈希函数:把以每个结点为根时的哈希值都存进多重集,再把多重集的哈希值算出来,进行比较(做法一)。
还可以通过找重心的方式来优化复杂度。一棵树的重心最多只有两个,只需把以它(们)为根时的哈希值求出来即可。接下来,既可以分别比较这些哈希值(做法二),也可以在有一个重心时取它的哈希值作为整棵树的哈希值,有两个时则取其中较小(大)的。
??? "做法一"
cpp --8<-- "docs/graph/code/tree-hash/tree-hash_2.cpp"
??? "做法二"
cpp --8<-- "docs/graph/code/tree-hash/tree-hash_3.cpp"
题目要求遍历一棵无根树产生的本质不同括号序列方案数。
首先可以注意到,两棵不同构的有根树一定不会生成相同的括号序列。我们先考虑遍历有根树能够产生的本质不同括号序列方案数,假设我们当前考虑的子树根节点为
通过上述 DP,可以求出根节点的方案数。再通过换根 DP,将父亲节点的哈希值和方案信息转移给儿子,可以求出以每个节点为根时的哈希值和方案数。每种不同的子树只需要计数一次即可。
??? "参考代码"
cpp --8<-- "docs/graph/code/tree-hash/tree-hash_4.cpp"
文中的哈希方法参考并拓展自博客 一种好写且卡不掉的树哈希。