The core idea of the whole process is intuitive to me so I’ll just leave some implemeational comments on stuff and doubts while writing.

example: company queries 1

  • use par as 0 and 1 indexed nodes, this’ll naturally fit in with up going out of bounds
  • the essence of the idea is that from u, to up 2k steps, you can go up 2(k-1) steps and then 2**(k-1) steps again
// problem: go up k levels
// tag: bin lift
 
#include <bits/stdc++.h>
using namespace std;
 
int main() {
#define int long long
	int n, q;
	cin >> n >> q;
	vector<vector<int>> g(n + 1);
	for (int i = 2; i <= n; i++) {
		int u;
		cin >> u;
		g[u].push_back(i);
		// now in this I'm given u -> i as input
		// so I could've filled up(u, 0) directly with no
		// dfs but in general if you given bidirectional
		// edges, you would need a "rooting" ( here it's
		// baked into the direction )
		// NOTE: think of what would dfs from 1 do,
		// you need top to down edges
	}
 
	const int D = (int)ceill(log2l(2e5 + 5));
	vector<vector<int>> up(n + 1, vector<int>(D + 1));
 
	auto fill0 = [&](auto &&f, int u, int par) -> void {
		// in this I use the convencion of 0 as no par
		// I've always used -1 but I think this new one is good too
		if (par)
			up[u][0] = par;
		for (int v : g[u])
			if (v != par)
				f(f, v, u);
	};
	fill0(fill0, 1, 0);
 
	for (int d = 1; d <= D; d++) {
		for (int i = 1; i <= n; i++) {
			// go 2^(d-1) up and then 2^(d-1) from there
			// using 0 to represent as NO more up is good implementationally
			// over say -1 as then the 0 naturally flows to up(0) which has
			// all elems 0 naturally
			up[i][d] = up[up[i][d - 1]][d - 1];
		}
	}
 
	auto lift = [&](int u, int k) {
		// the idea being you can represent k as powers of 2 -> binary
		// so going k up can be split into multiple steps of going up
		for (int i = 0; i <= D; i++) {
			if ((k >> i) & 1) {
				u = up[u][i];
			}
		}
		return u;
	};
 
	while (q--) {
		int u, k;
		cin >> u >> k;
		int res = lift(u, k);
		res = res == 0 ? -1 : res;
		cout << res << "\n";
	}
 
#undef int
}

example: company queries 2 ( lca )

  • in lca, remember to check if after maching levels, both are same and return if they are.
  • why go from D to 0 on finding a jump? I’m applying a greedy and it’s correctness depends on me rejecting what can’t be applied EVER so I would not want to use small jumps only to end up with largers jumps that I cannot use now.
#include <bits/stdc++.h>
using namespace std;
 
int main() {
#define int long long
	int n, q;
	cin >> n >> q;
	vector<vector<int>> g(n + 1);
 
	const int D = (int)ceill(log2l(2e5 + 5));
	vector<vector<int>> up(n + 1, vector<int>(D + 1));
 
	for (int i = 2; i <= n; i++) {
		int u;
		cin >> u;
		g[u].push_back(i);
		up[i][0] = u;
	}
 
	for (int d = 1; d <= D; d++) {
		for (int i = 1; i <= n; i++) {
			up[i][d] = up[up[i][d - 1]][d - 1];
		}
	}
 
	auto lift = [&](int u, int k) {
		for (int i = 0; i <= D; i++) {
			if ((k >> i) & 1) {
				u = up[u][i];
			}
		}
		return u;
	};
 
	vector<int> h(n + 1);
	auto hfill = [&](auto &&f, int u, int par) -> void {
		if (par)
			h[u] = h[par] + 1;
		for (int v : g[u])
			if (v != par)
				f(f, v, u);
	};
	hfill(hfill, 1, 0);
 
	auto lca = [&](int u, int v) {
		if (h[u] < h[v])
			swap(u, v);
		// invariant, h(u) >= h(v)
 
		int diff = h[u] - h[v];
		u = lift(u, diff);
 
		// WARN: remember, otherwise you start from the current lca
		// and the invariant that both are not same used in loop ahead
		// breaks
		if (u == v)
			return u;
 
		// now at same level
		// there MUST exist some k such that
		// moving just one level up from k, they both become same
		// and I have information about the bits of such k by the
		// fact that bit i is 1 if after moving that level up they
		// are both NOT same ( once you reach lca both must be same )
 
		// note that we cannot go in the other direction as it's
		// greedily searching for jumps ( think that we are ruling out
		// suffixes here ), if a suffix is ruled out in the MAX jump case
		// where we had not reduced the distance in any other way
		// that it's ruled out for good
		// if on the other hand many incorrect small jumps can make
		// a later larger jump impossible to make
		// you can even say this is binary search ( the 2^n variation )
		// where I try say 32 then 16 then 8 and so on
		for (int i = D; i >= 0; i--) {
			if (up[u][i] != up[v][i]) {
				u = up[u][i];
				v = up[v][i];
			}
		}
 
		return up[u][0]; // one above is the lca
	};
 
	while (q--) {
		int u, v;
		cin >> u >> v;
		cout << lca(u, v) << "\n";
	}
 
#undef int
}