Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Link Cut Tree #124

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions content/graph/LCT.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/**
* Author:
* Description: link-cut Tree. Supports BBST-like
PotatoHashing marked this conversation as resolved.
Show resolved Hide resolved
* augmentations. (Can be used in place of HLD).
* Current implementation supports update value at a node,
* and query max on a path.
* Tested on: http://acm.timus.ru/problem.aspx?num=1553
* Status: Passes existing fuzz tests (with function names modified).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll want to remove the old link-cut tree, so feel free to do that and modify the fuzz-test. Will also need to update the test to verify that max works (with max replaced by some non-commutative, non-associative function)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the fuzz-test to verify max for now. Will update it later for any new function that we decide on...

*/
struct Node {
bool flip = 0;
Node *pp, *p, *c[2];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible to add a one-line comment explaining what p and pp are? or are they standard names?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p = parent, pp = path-parent (and c = children).
p and c are straight forward. path-parent is from the Wikipedia article on LCTs and also this set of lecture notes https://courses.csail.mit.edu/6.851/spring12/scribe/L19.pdf (which I think the Wikipedia page is based on). Besides writing out the acronyms, do you think it's a good idea to explain what a path-parent is? I think that if you understand how LCTs work (by decomposing a tree into preferred paths), then knowing that pp stands for path-pointer should be enough information.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So something like // p ~ parent in segtree, pp = path parent? (Now, I'm also not sure whether that's helpful information when augmenting... but I could imagine such a comment being helpful for understanding)

// add stuff
int val, cval;
PotatoHashing marked this conversation as resolved.
Show resolved Hide resolved
Node() {
pp = p = c[0] = c[1] = 0;
val = cval = 0;
}
// lazy propogation
PotatoHashing marked this conversation as resolved.
Show resolved Hide resolved
void push() {
if (flip) {
if(c[0]) c[0]->flip ^= 1;
if(c[1]) c[1]->flip ^= 1;
PotatoHashing marked this conversation as resolved.
Show resolved Hide resolved
swap(c[0], c[1]); flip = 0;
}
// add stuff
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this comment as well -- I think we can instead write in the description how to augment it ("extend push/pull").

We'll also want a more thorough explanation and/or example -- it's not clear to me in which ways the LCT is similar to a segment tree. E.g. how do I do lazy updates on entire paths in the tree? Entire subtrees? Queries on subtrees? Can I use the flip bit myself for anything? How are things ordered? It's not reasonable to answer all of these questions (we should have a link in the description!), but some more info would be useful.

}
// combine values from children with self.
void pull() {
cval = val;
if(c[0]) c[0]->push(), cval = max(cval, c[0]->cval);
if(c[1]) c[1]->push(), cval = max(cval, c[1]->cval);
// add stuff
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work with non-commutative and non-associative operations?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've talked about this elsewhere, but it should work for non-commutative, associative functions (like a segment tree).

}
void rot(bool t) { /// start-hash
PotatoHashing marked this conversation as resolved.
Show resolved Hide resolved
Node *y = p, *z = y->p;
if (z) z->c[z->c[1] == y] = this;
p = z;
y->c[t] = c[!t];
if (c[!t]) c[!t]->p = y;
c[!t] = y;
y->p = this;
if (z) z->pull();
y->pull();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is an unreadable mess, it wouldn't hurt to golf it a bit and make it even more unreadable... some ideas:

  • !t is used more than t, maybe invert its meaning
  • , *&w = y->c[!t] at the top and use w instead of c[!t]
  • putting things on the same line is possibly worth it, if done tastefully... e.g. maybe y->p = this and p = z could go together?
  • some sort of "fix this node up" function which is called on some subset of c[!t]/this/y/z

Also, I'm a bit surprised the pull for z happens before that of y, I would have expected the other way around.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

, *&w = y->c[!t] at the top and use w instead of c[!t]

I might be misunderstanding, but why *&w instead of *w?
Also y->c[!t] is only used once, while this->c[!t] is used multiple times, so *&w = c[!t] might be better.

some sort of "fix this node up" function which is called on some subset of c[!t]/this/y/z

Like the fix function in the previous lct? (Set a node's children, and then fix will set those children's parents, but without the "(+ update sum of subtree elements etc. if wanted)" part, since that is what pull does).

Pull for z was unnecessary. (A node is only moved up one level for each call of rot, and then pulls up itself at the end of splay.) Thanks for noticing.

Everything else seems good.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, I confused y->c[!t] and c[!t]. c[!t] is assigned to, hence the &.

Like the fix function in the previous lct?

Yeah. I don't know much about how the previous lct works, but it seems like a method along those lines may be able to cut away a few lines? Again, I'm just throwing ideas out, I don't know whether it actually does save anything.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't look like adding fix will shorten it since w->p = y; is already as short as w->fix();. And then sometimes we'll have to set a parent directly anyway. (when z = 0 and we need to set this->p = z then we need to set p = z without calling z->fix() anyway). I think it's ok to leave as it is.
The other changes have been put in.

} /// end-hash
void xiao() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that we should probably rename this

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"propogatePathParent" is a little much though... Will keep thinking about possible alternatives.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could certainly shorten that to propPP if that's what we want to get across. But a nonsensical name would be fine as well

if (p) p->xiao(), pp = p->pp;
push();
}
void splay() { /// start-hash
xiao();
Node *y, *z;
while (p) {
y = p; z = y->p;
PotatoHashing marked this conversation as resolved.
Show resolved Hide resolved
bool t1 = (y->c[1] == this);
if (z) {
bool t2 = (z->c[1] == y);
if (t1 == t2) y->rot(t2), rot(t1);
else rot(t1), rot(t2);
} else rot(t1);
PotatoHashing marked this conversation as resolved.
Show resolved Hide resolved
}
pull();
} /// end-hash
Node* access() {/// start-hash
for (Node *y = 0, *z = this; z; y = z, z = z->pp) {
z->splay();
if (z->c[1]) z->c[1]->pp = z, z->c[1]->p = 0;
z->c[1] = y;
if (y) y->p = z;
z->pull();
}
splay();
return this;
} /// end-hash
};
struct LinkCut {
vector<Node> node;
PotatoHashing marked this conversation as resolved.
Show resolved Hide resolved
LinkCut(int N): node(N) {}
PotatoHashing marked this conversation as resolved.
Show resolved Hide resolved
Node* makeRoot(int u) {
PotatoHashing marked this conversation as resolved.
Show resolved Hide resolved
node[u].access()->flip ^= 1;
return &node[u];
}
Node* findRoot(int u) {
Node *x = node[u].access();
while(x->c[0]) x = x->c[0];
return x;
}
bool cut(int u, int v) { /// start-hash
Node *x = &node[u], *y = &node[v];
makeRoot(v);
x->access();
if (x->c[0] != y || y->c[1] != 0)
PotatoHashing marked this conversation as resolved.
Show resolved Hide resolved
return false;
x->c[0] = y->p = y->pp = 0;
x->pull();
return true;
} /// end-hash
bool isConnected(int u, int v) {
return findRoot(u) == findRoot(v);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

put this function on one line

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed, but goes slightly over the line limit now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm, that's no good then... Either revert or change the function names

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reverted

bool link(int u, int v) {
Node *y = &node[v];
if (isConnected(u, v)) return false;
makeRoot(u)->pp = y;
return true;
}
// Add c to node u.
void update(int u, int c) {
node[u].access()->val+=c;
PotatoHashing marked this conversation as resolved.
Show resolved Hide resolved
}
// Find max on the path from u to v.
ll query(int u, int v) {
PotatoHashing marked this conversation as resolved.
Show resolved Hide resolved
makeRoot(v);
return node[u].access()->cval;
}
};
PotatoHashing marked this conversation as resolved.
Show resolved Hide resolved