Tutorial: The fastest tree algorithm, the XOR Linked Tree

Правка en2, от pajenegod, 2024-10-18 00:46:04

Hi Codeforces!

This is a blog about a really cool folklore technique that seems to have been forgotten over time. I currently only know of one place where it is mentioned on the internet, https://www.algonotes.com/en/minimal-memory-dp/ (from 2018), but I've been told that it is far older than this. I don't know of a name for this technique, so I've decided to call it Xor Linked Tree because of its close connection to XOR linked lists https://en.wikipedia.org/wiki/XOR_linked_list .

This is a technique for solving tree problems using extremely little time/memory. For example, every fastest solution in the "Tree Algorithms" section on https://cses.fi/problemset/ currently uses this technique. So it is blazingly fast.

The idea:

Suppose in the input of a problem, you are given a tree as $$$n - 1$$$ edges. To construct the XOR Linked Tree data-structure from this, you go through the list of edges and store the following two things for every node in the tree:

  1. deg[node] = The degree of the node.
  2. xor[node] = The XOR of all neighbours of node.

Storing this will only take $$$2 n$$$ integers, so this is very cheap memory wise compared to an adjacency list. It is also much faster, since there are no expensive .push_back/.appends.

Claim: Using deg and xor, it is possible to reconstruct the tree.

Proof: Identify a leaf node in the tree (this can be done by finding a node with degree 1 in deg). Note that a leaf only has one neighbour, so the XOR of all of its neighbours is simply that neighbour. So the neighbour of a leaf is simply xor[leaf]. Now remove this leaf from the tree ("pluck it") by lowering the degree of its neighbour by one (deg[neighbour] -= 1) and XORing away leaf from xor[neighbour] (xor[neighbour] ^= leaf). Now we can just repeat this process of plucking leaves until there a single node left. We have now reconstructed the original tree.

Here is an implementation of the process described in the proof above ^

for node in range(n):
    while deg[node] == 1:
        nei = xor[node]

        # Pluck away node
        deg[node] = 0
        deg[nei] -= 1
        xor[nei] ^= node

        # Check if nei is now a leaf
        node = nei

Something to note is that it is possible to control which node is left at the end (the "root") by setting its degree to 0 before running the algorithm. So the final XOR Linked Tree traversal algorithm is

deg[root] = 0
for node in range(n):
    while deg[node] == 1:
        nei = xor[node]

        # Pluck away node
        deg[node] = 0
        deg[nei] -= 1
        xor[nei] ^= node

        # Check if nei is now a leaf
        node = nei

This is it! This is the entire algorithm.

Discussion

This tree traversal algorithm is very different from any other traversal algorithm, like DFS or BFS. For one, it doesn't use anything like a queue or a stack. Instead it simply removes one leaf using a while loop inside of a for loop. The order you traverse the nodes in is kind of like doing a BFS in reverse, but it is not exactly the same thing.

Discussion (Advanced)

One last thing I want to mention is that on some tree problems, you need specifically need a DFS ordering. For example, there are tree problems on cses.fi that requires you to compute LCA. Most if not all algorithms/data-structures for computing LCA is based on DFS. So does that mean that it is not possible to use XOR Linked Tree traversal algorithm for those problems?

The answer is no. It is possible to use the XOR Linked Tree traversal algorithm to compute a DFS ordering. The trick is first compute subtree sizes, and then use those to compute where in a DFS ordering each node should be.

TODO
Теги tree, traversal, xor, constant factor, memory limit, dfs, fast

История

 
 
 
 
Правки
 
 
  Rev. Язык Кто Когда Δ Комментарий
en35 Английский pajenegod 2024-10-19 14:16:01 47
en34 Английский pajenegod 2024-10-19 13:29:01 101 (published)
en33 Английский pajenegod 2024-10-18 23:22:01 415 Tiny change: 'xicPie9]. A big thanks to [user:' -> 'xicPie9]. Credit to [user:'
en32 Английский pajenegod 2024-10-18 23:11:09 32 Tiny change: ':9076307] from 201' -> ':9076307] by [user:Marcin_smu] from 201'
en31 Английский pajenegod 2024-10-18 23:06:04 12
en30 Английский pajenegod 2024-10-18 23:05:10 550 Tiny change: 'cy list | TBD | TBD | [243496' -> 'cy list | 2119 ms | 731.60 Mib | [243496'
en29 Английский pajenegod 2024-10-18 22:58:35 639
en28 Английский pajenegod 2024-10-18 02:53:10 30
en27 Английский pajenegod 2024-10-18 02:42:28 41
en26 Английский pajenegod 2024-10-18 02:41:41 4 Tiny change: 'is **yes**. It is possi' -> 'is **yes**, it is possi'
en25 Английский pajenegod 2024-10-18 02:40:25 4 Tiny change: 'mpared to DFS or BFS. For on' -> 'mpared to BFS or DFS. For on'
en24 Английский pajenegod 2024-10-18 02:32:59 6 Tiny change: 'mpared to an adjace' -> 'mpared to using an adjace'
en23 Английский pajenegod 2024-10-18 02:32:30 4 Tiny change: 'hbours of node.`\n\' -> 'hbours of the node.`\n\'
en22 Английский pajenegod 2024-10-18 02:31:12 7
en21 Английский pajenegod 2024-10-18 02:18:58 57
en20 Английский pajenegod 2024-10-18 02:14:41 4 Tiny change: 'Ring away leaf from' -> 'Ring away the leaf from'
en19 Английский pajenegod 2024-10-18 02:10:09 6 Tiny change: 'ittle time/memory. Fo' -> 'ittle time and memory. Fo'
en18 Английский pajenegod 2024-10-18 02:09:21 1 Tiny change: 'inked_list .\n\nThe X' -> 'inked_list.\n\nThe X'
en17 Английский pajenegod 2024-10-18 02:07:45 150
en16 Английский pajenegod 2024-10-18 01:42:13 10 Tiny change: ' one leaf using a w' -> ' one leaf at a time using a w'
en15 Английский pajenegod 2024-10-18 01:40:06 4 Tiny change: 'o call it _XOR Link' -> 'o call it the _XOR Link'
en14 Английский pajenegod 2024-10-18 01:37:10 25
en13 Английский pajenegod 2024-10-18 01:36:38 20
en12 Английский pajenegod 2024-10-18 01:35:21 5 Tiny change: 'lems, you need specifica' -> 'lems, you specifica'
en11 Английский pajenegod 2024-10-18 01:34:37 13 Tiny change: 'different than DFS or BF' -> 'different compared to DFS or BF'
en10 Английский pajenegod 2024-10-18 01:33:15 84
en9 Английский pajenegod 2024-10-18 01:31:55 13
en8 Английский pajenegod 2024-10-18 01:30:52 1 Tiny change: '/`.append`s calls.\n\' -> '/`.append` calls.\n\'
en7 Английский pajenegod 2024-10-18 01:30:17 13 Tiny change: 'o through the list of edges and' -> 'o through all of the edges and'
en6 Английский pajenegod 2024-10-18 01:29:29 17 Tiny change: 'st .\n\nThis is a tech' -> 'st .\n\nThe XOR Linked Tree is a tech'
en5 Английский pajenegod 2024-10-18 01:28:44 16
en4 Английский pajenegod 2024-10-18 01:26:02 1706 Tiny change: ' I saw it was being use' -> ' I saw it being use'
en3 Английский pajenegod 2024-10-18 00:49:58 3 Tiny change: ' algorithm. \n\n## Dis' -> ' algorithm!\n\n## Dis'
en2 Английский pajenegod 2024-10-18 00:46:04 1884 Tiny change: 'e are no `.push_back`/`.append`.\n\n**Cla' -> 'e are no `vector<vector>`s.\n\n**Cla'
en1 Английский pajenegod 2024-10-18 00:15:45 2085 Initial revision (saved to drafts)