Trees

Company Queries

// 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
}

Company Queries 2

// problem: lca
// 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);
 
	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
}

Distance Queries

// problem: fast pair distances
// tag: lca decomposition
 
#include <bits/stdc++.h>
using namespace std;
 
int main() {
	// finally needed to put these in for this problem
	cin.tie(nullptr);
	ios_base::sync_with_stdio(false);
 
#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 = 0; i < n - 1; i++) {
		int u, v;
		cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	vector<int> h(n + 1);
	auto hfill = [&](auto &&f, int u, int par) -> void {
		if (par) {
			h[u] = h[par] + 1;
			up[u][0] = par;
		}
		for (int v : g[u])
			if (v != par)
				f(f, v, u);
	};
	hfill(hfill, 1, 0);
 
	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;
	};
 
	auto lca = [&](int u, int v) {
		if (h[u] < h[v])
			swap(u, v);
 
		int diff = h[u] - h[v];
		u = lift(u, diff);
 
		if (u == v)
			return u;
 
		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
	};
 
	auto dist = [&](int u, int v) -> int {
		// say h(u) >= h(v) then two cases
		// lca(u,v) is v in which case ans is just a level diff(u,v)
		// = l(u) - l(v)
		// OR
		// lca(u,v) = a ( some diff node ) then it's simply
		// level diff(u,a) + level diff(v,a)
		// = l(u) - l(a) + l(v) - l(a)
		// notice that when a = v, this too collapse to first case
		// so can just use result from case 2
		return h[u] + h[v] - 2 * h[lca(u, v)];
	};
 
	// INFO: what is special about the lca here?
	// it's the common parent from which we can use some info
	// it decomposes the dfs direction into two parts cleanly
 
	while (q--) {
		int u, v;
		cin >> u >> v;
		cout << dist(u, v) << "\n";
	}
 
#undef int
}

Distance Queries (Centroid Decomposition)

// problem: fast (u,v) distance queries
// tag: centroid decomp
 
#include <bits/stdc++.h>
using namespace std;
 
int main() {
	// finally needed to put these in for this problem
	cin.tie(nullptr);
	ios_base::sync_with_stdio(false);
 
#define int long long
	int n, q;
	cin >> n >> q;
	vector<vector<int>> g(n + 1);
 
	for (int i = 0; i < n - 1; i++) {
		int u, v;
		cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
 
	vector<int> sz(n + 1), removed(n + 1), cd_par(n + 1), cd_depth(n + 1);
	// centroid_dist ( not cd_dist as it's not centroid decomposition dist )
	vector<vector<int>> c_dist(n + 1);
 
	auto fsz = [&](auto &&f, int u, int par) -> int {
		sz[u] = 1;
		for (int v : g[u]) {
			if (v == par || removed[v])
				continue;
			sz[u] += f(f, v, u);
		}
		return sz[u];
	};
 
	auto fcentroid = [&](auto &&f, int u, int par, int tot) -> int {
		for (int v : g[u]) {
			if (v == par || removed[v])
				continue;
			if (sz[v] > tot / 2)
				return f(f, v, u, tot);
		}
		return u;
	};
 
	auto fdist = [&](auto &&f, int u, int par, int d) -> void {
		// for each node in the component with the centroid, centroid
		// starts as root and we fill in a dist
		// note that for same node, this dfs will never push twice
		c_dist[u].push_back(d);
		for (int v : g[u]) {
			if (v == par || removed[v])
				continue;
			f(f, v, u, d + 1);
		}
	};
 
	auto fcd = [&](auto &&f, int u, int par) -> void {
		// note that par IS 0 as these are just individual trees and
		// meant to be treated as such
		int tot = fsz(fsz, u, 0);
		int c = fcentroid(fcentroid, u, 0, tot);
 
		// USE the centroid to fill info, note that you CANNOT remove
		// it yet
		fdist(fdist, c, 0, 0);
		if (par) {
			cd_depth[c] = cd_depth[par] + 1;
			cd_par[c] = par;
		}
 
		// now remove
		removed[c] = 1;
 
		for (int v : g[c]) {
			// this v == par is redundant as parent must be SOME centroid
			// and MUST have been removed in the prev iteration
			if (v == par || removed[v])
				continue;
			f(f, v, c);
		}
	};
	fcd(fcd, 1, 0);
 
	vector<int> seen(n + 1);
	int ctr = 0;
	auto dist = [&](int u, int v) -> int {
		// the idea is that you do ramp trick here for seen
		// and find the lowest centroid ancestor ( cca )
		// ^ is the centroid that split it into two components ( so u,v split to
		// siblinds trees now ). so if you decompose the path alon that, and you
		// have distances in both, you can sum them up
 
		++ctr;
		for (int x = u; x; x = cd_par[x]) {
			seen[x] = ctr;
		}
 
		// from v go up till you hit seen
		for (int x = v; x; x = cd_par[x]) {
			if (seen[x] == ctr) {
				// hit a seen
				// note that for the cd tree, root is the first elem in cd dist
				// then for the node, next is dist to whatever is NEXT centroid
				// in the child tree and so on, so dist(u) => {c0, c1, c2...}
				// where ci is the ith node as you move from root to the current
				// node IN the cd tree. to summarise for each original node u,
				// dist_to_cd[u] stores distances to the centroid ancestors of
				// u, ordered by centroid depth:
				//
				// now ^ isn't self evident, after all, the depth DOES not
				// depent on u, where we started from, even though we are
				// checking u's dist array. Depth in cd tree would be the entry
				// in cd_dist(u). This is not obvious why -- but it's due to the
				// fill
				//
				// this of it as, x MUST be a a centroid ancestor, that is by
				// contruction here if this is then at dep 2 in the cd tree,
				// then at dep 1, and at dep 0, we also had a centroid ancestor,
				// and they TOO must've pushed a dist value corresponding to
				// them so this cd_depth is VERY much rooted into x being an
				// ancestor in cd tree
				int ans = c_dist[u][cd_depth[x]] + c_dist[v][cd_depth[x]];
				return ans;
			}
		}
 
		return -1ll;
	};
 
	while (q--) {
		int u, v;
		cin >> u >> v;
		cout << dist(u, v) << "\n";
	}
 
#undef int
}

Subordinates

// problem: subtree size for each node
 
#include <bits/stdc++.h>
using namespace std;
 
int main() {
	int n;
	cin >> n;
	vector<vector<int>> g(n + 1);
	for (int i = 2; i <= n; i++) {
		int v;
		cin >> v;
		// boss is v
		g[v].push_back(i);
	}
 
	vector<int> sizes(n + 1);
	auto f = [&](auto &&f, int u) -> int {
		auto &sz = sizes[u];
		for (int v : g[u])
			sz += f(f, v);
		return ++sz;
	};
	f(f, 1);
 
	for (int i = 1; i <= n; i++)
		cout << sizes[i] - 1 << " ";
	cout << "\n";
}

Tree Diameter

// problem: max distance bw two nodes
// once more a postorder dp with global optim
 
#include <bits/stdc++.h>
using namespace std;
 
int main() {
	int n;
	cin >> n;
	vector<vector<int>> g(n + 1);
	for (int i = 0; i < n - 1; i++) {
		int u, v;
		cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
 
	// there is the usual graph algorithm but for a tree I can do
	// I can do the usual postorder dp where I optimise a global
	// max and return localised best dp states
 
	int ans = 0;
	auto f = [&](auto &&f, int u, int par) -> int {
		array<int, 2> top2{};
		for (int v : g[u]) {
			if (v == par)
				continue;
			int cur = f(f, v, u);
			if (cur >= top2[0]) {
				top2[1] = top2[0];
				top2[0] = cur;
			} else if (cur > top2[1]) {
				top2[1] = cur;
			}
		}
		ans = max(ans, top2[0] + top2[1] + 1);
		return 1 + top2[0];
	};
	f(f, 1, -1);
 
	// I calc dist as nodes count
	cout << ans - 1 << "\n";
}

Tree Distances 1

// problem: for each node the max distance to some other node
// tag: edge message dp
 
#include <bits/stdc++.h>
using namespace std;
 
int main() {
	int n;
	cin >> n;
	vector<vector<int>> g(n + 1);
	for (int i = 0; i < n - 1; i++) {
		int u, v;
		cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
 
	struct top2 {
		array<int, 2> tag{};
		array<int, 2> arr{};
		void add(int key, int val) {
			if (val >= arr[0]) {
				arr[1] = arr[0];
				arr[0] = val;
				tag[1] = tag[0];
				tag[0] = key;
			} else if (val > arr[1]) {
				arr[1] = val;
				tag[1] = key;
			}
		}
		array<int, 2> best() { return {tag[0], arr[0]}; }
		array<int, 2> other(int key) {
			if (key == tag[0])
				return {tag[1], arr[1]};
			return best();
		}
	};
 
	// now for split on u-v edge, we need up and down
	// now up is fine but down needs to be NOT across the u-v cut that
	// we are making
	vector<int> up(n + 1);
	vector<top2> down(n + 1);
 
	// if you fill these we can have a final MERGE step
	// this fills msg(u -> par(u))
	auto downf = [&](auto &&f, int u, int par) -> int {
		// by dfs this partition parition would be seprate
		// from parent
		auto &d = down[u];
		for (int v : g[u]) {
			if (v == par)
				continue;
			// you can think of this as the sep even
			// because re-root / messages go in both directions
			d.add(v, 1 + f(f, v, u));
			// READ AS: for u, branch v, produced this value
		}
		return d.best()[1];
	};
	downf(downf, 1, -1);
 
	// this'll fill msg( u <- par(u) )
	auto upf = [&](auto &&f, int u, int par) -> void {
		if (par != -1) {
			up[u] = 1 + max(up[par], down[par].other(u)[1]);
		}
		for (int v : g[u])
			if (v != par)
				f(f, v, u);
	};
	upf(upf, 1, -1);
 
	// MERGE, you can think of this as the "re"-root as for a node
	// I consider ALL edges similar to the root case ( par as -1 )
	for (int u = 1; u <= n; u++) {
		cout << max(up[u], down[u].best()[1]) << " ";
	}
	cout << "\n";
}

Tree Distances 2

// problem: for each node the sum distance to all others node
// tag: edge message dp
 
#include <bits/stdc++.h>
using namespace std;
 
int main() {
#define int long long
	int n;
	cin >> n;
	vector<vector<int>> g(n + 1);
	for (int i = 0; i < n - 1; i++) {
		int u, v;
		cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
 
	vector<int> sz(n + 1);
	int ans1 = 0; // for 1 as root
	auto f = [&](auto &&f, int u, int par, int d) -> int {
		ans1 += d;
		auto &a = sz[u] = 1;
		for (int v : g[u])
			if (v != par)
				a += f(f, v, u, d + 1);
		return a;
	};
 
	f(f, 1, -1, 0);
 
	vector<int> ans(n + 1);
	ans[1] = ans1;
 
	// I need to fill it in dfs order
	auto f2 = [&](auto &&f, int u, int par) -> void {
		if (par != -1) {
			ans[u] = ans[par] + n - 2 * sz[u];
		}
		for (int v : g[u])
			if (v != par)
				f(f, v, u);
	};
	f2(f2, 1, -1);
 
	for (int i = 1; i <= n; i++)
		cout << ans[i] << " ";
	cout << "\n";
#undef int
}

Tree Matching

// problem: pick max edges where each node is only on ONE edge
// usual postorder dp
 
#include <bits/stdc++.h>
using namespace std;
 
int main() {
	int n;
	cin >> n;
	vector<vector<int>> g(n + 1);
	for (int i = 0; i < n - 1; i++) {
		int u, v;
		cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
 
	// f(u,1) => I include an edge from u to u.c
	// f(u,0) => I don't do that
	const int INF = (int)1e9+5;
 
	auto f = [&](auto &&f, int u, int par) -> array<int,2> {
		array<int,2> ret{};
 
		vector<array<int,2>> cs;
		for(int v:g[u]){
			if(v==par) continue;
			cs.push_back(f(f,v,u));
		}
 
		// 0
		for(auto &c:cs){
			ret[0] += max(c[0],c[1]);
		}
		// 1
		int best = -INF; // leaf case
		for(auto& c:cs){
			int tans = ret[0] - max(c[0],c[1]) + c[0];
			best = max(best, tans);
		}
		ret[1] = best == -INF? best : best+1;
 
		return ret;
	};
 
	auto ans = f(f,1,-1);
	cout<<max(ans[0],ans[1])<<"\n";
}