When determining whether some trees are isomorphic, we often convert these trees into hash values for storage to reduce complexity.
Tree hashing is flexible, allowing various hashing methods to be designed; however, if designed arbitrarily, it may be incorrect and could be exploited. The following introduces a class of methods that are easy to implement and difficult to exploit.
Method
This class of methods requires a hash function for multisets. The hash value of a subtree rooted at a certain node is the hash value of the multiset formed by the hash values of all its child subtrees, i.e.:
\[
h_x = f(\{ h_i \mid i \in son(x) \})
\]
where \(h_x\) denotes the hash value of the subtree rooted at \(x\), and \(f\) is the hash function for multisets.
Taking the hash function used in the code as an example:
\[
f(S) = \left( c + \sum_{x \in S} g(x) \right) \bmod m
\]
Here, \(c\) is a constant, generally set to \(1\). \(m\) is the modulus, commonly using \(2^{32}\) or \(2^{64}\) for natural overflow, or a large prime number. \(g\) is a mapping from integers to integers; the code uses xor shift, but other functions can be chosen, though polynomials are not recommended. To prevent adversarial exploitation targeting xor hash, a random constant can be XORed before and after the mapping.
This hashing method is very easy to write. If rerooting is needed, simply subtract the subtree hash during the second DP.
The isomorphism mentioned in this problem refers to unrooted trees, while the method introduced above is for rooted trees. Therefore, only when the roots are the same will two isomorphic unrooted trees have the same hash value. Since the data range is small, we can brute-force compute the hash value for each node as the root and compare after sorting.
If the data range is large, we can also use rerooting DP, traversing the tree twice to compute the hash value for each node as the root. We can also utilize the multiset hash function mentioned above: store the hash values for each node as the root into a multiset, then compute the hash value of the multiset for comparison (Method 1).
We can also optimize complexity by finding the centroid(s). A tree has at most two centroids; we only need to compute the hash value(s) with it (them) as the root. Then, we can either compare these hash values separately (Method 2), or when there is one centroid, take its hash value as the tree's hash value; when there are two, take the smaller (or larger) one.
The problem asks for the number of essentially different bracket sequences generated by traversing an unrooted tree.
First, note that two non-isomorphic rooted trees will never generate the same bracket sequence. We first consider the number of essentially different bracket sequences generated by traversing a rooted tree. Suppose the current subtree root is \(u\), and let \(f(u)\) denote the number of sequences for this subtree. Starting from \(u\), the traversal order can be chosen arbitrarily, yielding \(|son(u)|!\) permutations. For each child node \(v\), its subtree has \(f(v)\) sequences. Thus, \(f(u)=|son(u)|! \cdot \prod_{v \in son(u)} f(v)\). However, isomorphic subtrees will cause duplicates, so \(f(u)\) must be divided by the product of factorials of the counts of each essentially different subtree type, similar to permutations of a multiset.
Using this DP, we can compute the number of sequences for the root node. Then, via rerooting DP, transferring the parent node's hash value and sequence information to children, we can compute the hash value and sequence count for each node as the root. Each distinct subtree type only needs to be counted once.