By Vladosiya, history, 4 weeks ago, translation,

I found a small shortage of materials in English on this topic and I want to start with translated article of rationalex:

## The problem

We want to learn how to compare root trees for isomorphism (equality up to the renumbering of vertices + the root of one tree necessarily goes to the root of another tree).

## Hash of vertex

Note that since we cannot appeal to the vertex numbers, the only information we can operate on is the structure of our tree.

We will consider as a hash of a vertex without children some constant (for example, 179), and for a vertex with children we will use as a hash some function from the sorted list of hashes of children (since we do not know the true order in which the children should go, we need to bring them to the same form). The hash of the root tree will be considered the hash of the root.

By construction, the hashes of isomorphic root trees coincide (the author leaves the proof by induction on the number of levels in the tree to the reader as an exercise).

## Polynomial hash is not suitable

Consider 2 trees:

If we calculate for them to take a polynomial hash as a function of children, we get: $h(T1)=179+179p+179p^2=179+p(179+179p)=h(T2)$

## Which hash is suitable?

As a good hash function, for example, this is suitable

$h(v)=42 + \sum_{u \in sorted\_by\_hash(child(v))} log(h(u))$

For this hash function, it may seem that it is possible not to sort the hashes of children, but this is not the case, because when calculating floating-point numbers, we have an error, and in order for this summation result to be the same for isomorphic trees, it is also necessary to sum the children in the same order.

An example of a more complicated hash function:

$h(v)= \big[\sum_{u \in sorted\_by\_hash(child(v))} h(u)^2+h(u)p^i+42\big]\mod2^{64}$

## Asymptotics

All we need to do at each level is sorting the vertices by hash value and summing, so the final complexity is: $O(|V|log(|V|))$

## I want to continue on my own:

In the reality of Codeforces, these approaches have problems in the form of hacks (which can be seen, for example, by hacks of this task). Therefore, I want to talk about an approach in which there are no collisions.

## What is this magic hash function?

Let's sort the hashes of the children for the vertex and match the number to this array, which we will consider the hash of the vertex (if the array is new, then we will assign it the minimum unoccupied number, otherwise we will take the one that has already been given).

## Why does it work fast?

It is easy to notice that the total size of the arrays that we counted is $n - 1$ (each addition is a transition along the edge). Due to this, even using treemap for mapping, all accesses to it will require a total of $O(n \cdot log(n))$. Comparing a key of size $sz$ with another key works in $O(sz)$ and such comparisons for each key will occur $O(\log(n))$ times, and the sum of all $sz$, as we remember, is $n-1$, so it turns out a total of $O(n\cdot \log(n))$. (You might think that it is worth using hashmap, but this does not improve the asymptotics and causes the probability of a collision).

• +151

 » 4 weeks ago, # | ← Rev. 3 →   +25 First of all, what do you call a "polynomial hash"? Is it something like sum of "take $i$-th child, multiply its hash by $p^i$"? If it is true, then this "hash" changes when we swap two children with different hashes, which shouldn't happen.UPD: okay, I wasn't very observant and didn't read the whole paragraph with "hash of subtree is hash of sorted list of smth"Second, I think that the last way you mentioned is the most popular and simple, but just out of theoretical interest one can read this ancient article by rng_58. It contains two pictures which seem to have expired, but I have the second of them: the relevant pic
•  » » 4 weeks ago, # ^ |   +11 I tried the approach mentioned in the rng_r8's blog (Multivariable polynomial hashing) but got wa on test case 7. Did I implement it in the wrong way or did the main case had the hack input for which it would give wa.submission link
•  » » » 13 days ago, # ^ |   0 You did something wrong. My solution got AC
•  » » » » 12 days ago, # ^ |   0 Thanks people_plus_plusI compared my code with yours and changed two things and it got accepted.Firstly, for all vertex my code did....hash[vertex] *= (depth[vertex] + hash[child]), but I didn't mutliply the hash of that vertex with its own depth. (I didn't multiply the hash of parent which of course was 0 during that time).It still gave wa.Then I used mod in the hash multiplication instead of just purely using unsigned long long which automatically mods any value greater than unsigned long long max with unsigned long long max.However, In rng's blog, he didn't tell to multiply the hash with depth of that vertex itself.Also, it would be really helpful if you can tell me what is the reason that using a smaller MOD (1e9 + 9) instead of a larged MOD (unsigned long long) avoided collision. Should I do that every time I use hashing?
•  » » » » » 12 days ago, # ^ | ← Rev. 2 →   +6 Where is a reason why you should not use auto mod like you did in your first submission. Standart types use mod $2^x$ and it can be easily get collision even on small data! You can read about this here https://codeforces.com/blog/entry/4898?locale=ruIn simple words: dount use automatically mods, use your own mods like $10^9 + 7$. To be sure, use int64 mods. Be carefull when you use hash on codeforces, because there are hacks. You can avoid being hacked by choose a random prime-mod number.
 » 4 weeks ago, # |   -150 Did you take any permission to publish my algorithm?
 » 4 weeks ago, # |   +31 There's a way to hash rooted trees which has a better time complexity and is also easy to implement, whose expected number of collisions doesn't exceed $O(\frac{n^2}{2^w})$. It is mentioned here.To avoid being hacked, you can use random parameter instead of 1237123 in the article when solving Codeforces problems.
•  » » 11 days ago, # ^ |   0 Can you explain the proof of the expected number of collisions and the meaning of $w$ in $2^w$? Thank you.
 » 4 weeks ago, # | ← Rev. 2 →   +31 Polynomial hash works fine, you just need to represent tree's euler tour as a correct bracket sequence — ( when you walk along an edge from parent to child and ) when you go backward from child to parent. So T1 will be ()()() and T2 will be ()(()()). Then hash it as usual string (of course, with sorting childs' hashes).
•  » » 4 weeks ago, # ^ | ← Rev. 2 →   0 Doesn't it look like it's easier to create a collision with such hashing? (In context of hacks)
•  » » » 4 weeks ago, # ^ |   0 Every tree is just a string in such representation. If we use polynomial hashing for strings, why shouldn't we use it in this case?
•  » » » » 4 weeks ago, # ^ |   0 I solved https://codeforces.com/contest/1800/problem/G using your idea. It's nice
 » 4 weeks ago, # | ← Rev. 3 →   +6 Can anybody suggest some problems on the topic?UPD:1800G - Symmetree763D - Timofey and a flat treeKattis — Two Charts Become One
•  » » 4 weeks ago, # ^ |   +7 I recently solved a problem from an ICPC regionals using this technique: Two Charts Become One
 » 4 weeks ago, # |   +111 It seems often a good idea to compute not hashes for subtrees, but simply numeric representations $val(u)$ — consecutive integers from $0$ and beyond, where $u$ is the root, which defines a subtree rooted at $u$. The values $val(u)$ and $val(v)$ will be equal if and only if the subtrees with roots $u$ and $v$ are equal (or isomorphic).You can just use map, int> vals for memoization. Then if for $u$ you need to calculate the value of $val(u)$, then take all children $w_1, w_2, \dots, w_k$ of $u$ and make a vector $[val(w_1), val(w_2), \dots, val(w_k)]$. If it has a value in $vals$, then take the value from there, if not, enter the next integer (well, just use current $vals.size()$).And just recursively calculate this for all subtrees. In total, it will work for $O(nlogn)$, apparently. And no issues with possible hash collisions. You can think of the resulting values as perfect hashes.
•  » » 4 weeks ago, # ^ |   0 don't we need to sort the vector ([val(w1),val(w2),…,val(wk)]) ?
•  » » » 4 weeks ago, # ^ |   0 Yeah i think we need to sort them.
•  » » 4 weeks ago, # ^ |   0 Thank you! i literally do not know about hashing at all. i just saw your comment and solved the problem 1800G - Symmetree .My submission if anyone want :- 195934874
•  » » » 4 weeks ago, # ^ |   0 Hey, may you please explain a little more on how this algorithm works? I am not able to understand... Please!
•  » » » » 3 weeks ago, # ^ | ← Rev. 3 →   +18 Here's how I understand it, and hopefully it helps you.We want to assign a number for every node $u$ (we write this as $val(u)$) based on the structure of its children (by looking at $childrenInfo(u) = [val(w_1), val(w_2), ..., val(w_k)]$ where $w_1, w_2, \dots, w_k$ are children of $u$). So, two nodes with the same $childrenInfo$ will have the same value in $val$ and have the same subtree structure (or isomorphic). For example, in this tree: Nodes $4$, $5$, $6$, $9$, and $12$ have no children (so each $childrenInfo$ is $[$ $]$), so we assign $val(4)$, $val(5)$, $val(6)$, $val(9)$, and $val(12)$ the same number, say 0. Nodes $3$, $8$, and $11$ have the same children info, since $childrenInfo(3) = [val(5)] = [0]$, $childrenInfo(8) = [val(9)] = [0]$, and $childrenInfo(11) = [val(12)] = [0]$. That means we assign $val(3)$, $val(8)$, and $val(12)$ the same number but different from the previous one, say 1. Nodes $2$ and $7$ have the same children info, since $childrenInfo(2) = [val(4), val(3)] = [0,1]$ and $childrenInfo(7) = [val(8), val(6)] = [1,0]$. Note that the order of children does not matter in $childrenInfo$, so $[0,1]$ and $[1,0]$ are the same (we can sort them first before comparing). So we assign $val(2)$ and $val(7)$ the same number, say 2. Node $10$ has $childrenInfo(10) = [val(11)] = [1]$. Let's assign $val(10) = 3$. Node $1$ has $childrenInfo(1) = [val(2), val(7), val(10)] = [2,2,3]$. Let's assign $val(1) = 4$. The algorithm works like DFS. So to calculate for $val(u)$, we want to first get $childrenInfo(u)$. Notice that to get $childrenInfo(u)$, we have to know $val(w_i)$ for all of its children $w_i$ first. So do the DFS like the following code snippet. Note that we use vals to store/remember what number to assign to $val(node)$ with certain $childrenInfo$. Codevector val(n, -1); map, int> vals; void dfs(int node, int parent) { // we run DFS for all child first (so that val for children is calculated), // then we add the val of the child to childrenInfo after. vector childrenInfo; for (int child : adj[node]) { if (child != parent) { dfs(child, node); childrenInfo.push_back(val[child]); } } sort(childrenInfo.begin(), childrenInfo.end()); if (vals.count(childrenInfo) == 0) { // if it is the first time we encounter a node with // this childrenInfo, then we assign it a new number (like vals.size()). vals[childrenInfo] = vals.size(); } val[node] = vals[childrenInfo]; } // then run dfs(root, -1) 
•  » » » » » 3 weeks ago, # ^ |   0 Wow, great explanation, understood completely :) Thanks for such a detailed comment.
•  » » » » » » 3 weeks ago, # ^ |   0 You are welcome! :)
•  » » » » » 3 weeks ago, # ^ | ← Rev. 2 →   0 I believe storing hash values for all the subtrees will lead to $O(n^2)$ memory complexity. Please correct me if I am wrong.Upd — nvm i understood how it's $O(n)$
•  » » 4 weeks ago, # ^ |   0 Does anyone know proof of the complexity of this algorithm?
•  » » » 4 weeks ago, # ^ |   0 Proofed in the end of post?
•  » » » » 4 weeks ago, # ^ | ← Rev. 2 →   0 I'm asking about an algorithm Mike presented.
•  » » » » » 4 weeks ago, # ^ |   +13 This is the algo I described in the end)
•  » » » » » » 4 weeks ago, # ^ |   +3 Oh shit you're right, thanks.
 » 9 days ago, # |   0 I just want to know what should be the most suitable hash function for root tree?Hash[node]=(product of (Hash[child]+degree(node))*(prime^x)%mod where x=number of child and mod=1e9+7;
 » 9 days ago, # |   0 Relevant problem: https://cses.fi/problemset/task/1700/