def test_makeRoot_path_shouldFlipPath(self): tree = LinkCutTree() a1 = tree.makeTree('a1') a2 = tree.makeTree('a2') a3 = tree.makeTree('a3') a4 = tree.makeTree('a4') a5 = tree.makeTree('a5') a6 = tree.makeTree('a6') tree.link(a1,a2,1) tree.link(a2,a3,2) tree.link(a3,a4,3) tree.link(a4,a5,4) tree.link(a5,a6,5) #act tree.makeRoot(a6) #assert self.assertEqual(a6, tree.getRoot(a1)) self.assertEqual(a6, a5.represented_parent) self.assertEqual(a5, a4.represented_parent) self.assertEqual(a4, a3.represented_parent) self.assertEqual(a3, a2.represented_parent) self.assertEqual(a2, a1.represented_parent)
def test_makeRoot_path_shouldFlipPath(self): tree = LinkCutTree() a1 = tree.makeTree('a1') a2 = tree.makeTree('a2') a3 = tree.makeTree('a3') a4 = tree.makeTree('a4') a5 = tree.makeTree('a5') a6 = tree.makeTree('a6') tree.link(a1, a2, 1) tree.link(a2, a3, 2) tree.link(a3, a4, 3) tree.link(a4, a5, 4) tree.link(a5, a6, 5) #act tree.makeRoot(a6) #assert self.assertEqual(a6, tree.getRoot(a1)) self.assertEqual(a6, a5.represented_parent) self.assertEqual(a5, a4.represented_parent) self.assertEqual(a4, a3.represented_parent) self.assertEqual(a3, a2.represented_parent) self.assertEqual(a2, a1.represented_parent)
def test_makeRoot_flip_tree(self): tree = LinkCutTree() a1 = tree.makeTree('a1') a2 = tree.makeTree('a2') a3 = tree.makeTree('a3') a4 = tree.makeTree('a4') a5 = tree.makeTree('a5') a6 = tree.makeTree('a6') tree.link(a1,a2,1) tree.link(a2,a3,2) tree.link(a3,a4,3) tree.link(a4,a5,4) tree.link(a5,a6,5) b1 = tree.makeTree('b1') b2 = tree.makeTree('b2') b3 = tree.makeTree('b3') tree.link(b1,b2) tree.link(b2,b3) tree.link(a3,b1) #act tree.makeRoot(b3) #assert self.assertEqual(b3, tree.getRoot(a6)) # nodes on the flipped path still share root self.assertEqual(tree.getRoot(b1), tree.getRoot(a2)) # node in flipped path and node out of path share root self.assertEqual(tree.getRoot(a6), tree.getRoot(a2)) self.assertEqual(b2,b1.represented_parent) self.assertEqual(b3,b2.represented_parent) self.assertEqual(b1,a3.represented_parent) self.assertEqual(a3,a2.represented_parent) self.assertEqual(a2,a1.represented_parent)
def test_makeRoot_flip_tree(self): tree = LinkCutTree() a1 = tree.makeTree('a1') a2 = tree.makeTree('a2') a3 = tree.makeTree('a3') a4 = tree.makeTree('a4') a5 = tree.makeTree('a5') a6 = tree.makeTree('a6') tree.link(a1, a2, 1) tree.link(a2, a3, 2) tree.link(a3, a4, 3) tree.link(a4, a5, 4) tree.link(a5, a6, 5) b1 = tree.makeTree('b1') b2 = tree.makeTree('b2') b3 = tree.makeTree('b3') tree.link(b1, b2) tree.link(b2, b3) tree.link(a3, b1) #act tree.makeRoot(b3) #assert self.assertEqual(b3, tree.getRoot(a6)) # nodes on the flipped path still share root self.assertEqual(tree.getRoot(b1), tree.getRoot(a2)) # node in flipped path and node out of path share root self.assertEqual(tree.getRoot(a6), tree.getRoot(a2)) self.assertEqual(b2, b1.represented_parent) self.assertEqual(b3, b2.represented_parent) self.assertEqual(b1, a3.represented_parent) self.assertEqual(a3, a2.represented_parent) self.assertEqual(a2, a1.represented_parent)
class RetroactiveUnionFind(object): """Fully retroactive union find implemented using link-cut trees to represented disjoint forests""" def __init__(self): self.forest = LinkCutTree() self.time = 0 # unionAgo(a,b) links nodes a and b in the LinkCutTree if they weren't already linked. # If they were it cuts the latest edge on the path between a and b (if it is later than the new link time). # The new subtree will contain exactly one of a and b. We make that node the root of the subtree and link it to # the other node still in the main tree. # This preserves old unions because any edge below the cut edge will now follow a path through the a-b edge up to # the lca, which is guaranteed not to have an edge value greater than the cut edge. Any edge above the cut \ # edge will not be affected. def unionAgo(self, a_data, b_data, tdelta=0): # if the sets are already connected at the specified time return if self.sameSetAgo(a_data, b_data, tdelta): return #get node objects to work with a = self.forest.getNode(a_data) b = self.forest.getNode(b_data) union_time = self.time + tdelta if a is None: a = self.forest.makeTree(a_data) if b is None: b = self.forest.makeTree(b_data) # if the nodes are not connected at all, connect them. If they are connected at a later time, # cut the oldest edge on the path between the two nodes, make the union'ed node the root of that tree # and attach it to the other union'ed node. if self.forest.getRoot(a) != self.forest.getRoot(b): self.forest.makeRoot(b) self.forest.link(a, b, union_time) else: lca = self.forest.lca(a, b) max_time = float("-inf") max_time_node = None for next in [a, b]: while next is not lca: if next.parent_edge_weight > max_time: max_time = next.parent_edge_weight max_time_node = next next = next.represented_parent self.forest.cut(max_time_node) if self.forest.getRoot(a) == next: self.forest.makeRoot(a) self.forest.link(b, a, union_time) else: self.forest.makeRoot(b) self.forest.link(a, b, union_time) self.time += 1 # sameSetAgo(a,b,t) will find the lca of a and b and traverse the path from both to the lca, # finding the largest edge on the path between a and b. If any edge is larger than time + tdelta then a, and b # were not connected at (time + tdelta) def sameSetAgo(self, a_data, b_data, tdelta=0): # sameset is reflexive if a_data == b_data: return True a = self.forest.getNode(a_data) b = self.forest.getNode(b_data) query_time = self.time + tdelta if a is None or b is None: return False lca = self.forest.lca(a, b) if lca is None: return False for next in [a, b]: while next is not lca: if next.parent_edge_weight > query_time: return False next = next.represented_parent return True # sameSetWhen(a,b) traverses the path between a and b and return the largest edge, # which is the time at which a and b were connected. def sameSetWhen(self, a, b): lca = self.forest.lca(a, b) if lca is None: return float("-inf") max_time = float("-inf") for next in [a, b]: while next is not lca: if next.parent_edge_weight > max_time: max_time = next.parent_edge_weight next = next.represented_parent return max_time