G. Sakurako and Chefir

来源:Codeforces Round 981 (Div. 3)

链接:Problem - G - Codeforces
思路:
  1. 题意是向上走会损失体力,向下走不会损失体力,赛时看反了,搞了好久自己编的问题。
  2. 引理1:在一棵树上,距离一个点最远的点是树的两个直径端点
  3. 引理2:小的子树的直径一定小于大的子树
  4. 引理3:包含一个子树 U U U的更大树的直径端点一定距离这个子树 U U U内的点更远
  5. 由引理3,我们可以推导出来,我们要尽可能地向上走到距离该点最远的祖先 v v v
  6. 由引理1,我们可以推导出 v v v的子树的直径的一个端点就是距离所求点的最远点。
  7. 所以我们首先考虑如何到达距离该点最远的祖先 v v v,这里我们可以先求出整棵树的 L C A LCA LCA,然后使用类似求 l c a lca lca的二进制拆分的跳点方式向上跳 k k k个点。
		int v, k;int ret = k;
		cin >> v >> k;
		int now = v;
		int tip = 0;
		if (k >= dep[v]) {
			now = 1;
		}
		else {//倍增向上跳点到距离x为k的祖先上
			while (k) {
				if (k & 1) {
					now = lca.fa[now][tip];
				}
				k >>= 1;
				tip++;
			}
		}	
  1. 而下一步我们就要求出所有点的子树的直径。
  2. 引理4:设两棵树的直径端点点对分别为{ u 1 , u 2 u_1,u_2 u1,u2},{ u 3 , u 4 u_3,u_4 u3,u4}合并两颗树的时候,所形成的新的直径端点 n v 1 , n v 2 ∈ nv_1,nv_2\in nv1,nv2{ u 1 , u 2 , u 3 , u 4 u_1,u_2,u_3,u_4 u1,u2,u3,u4},也就是说,新的直径的两个端点,一定是之前两棵树直径的四个端点之二。
  3. 所以由引理4,我们就可以愉快地将一个点的两棵子树合并起来,并且求出直径了。
#include<bits/stdc++.h>
using namespace std;
#define ll  long long 

struct lca_st {

	vector<int>dep;
	vector<vector<int>>fa;

	void init(int n) {
		fa = vector<vector<int>>(n + 1, vector<int>(30));
		dep = vector<int>(n + 1);
		dep[0] = -1;
	}

	void dfs(int u, int father, vector<vector<int>>& e) {
		dep[u] = dep[father] + 1;
		fa[u][0] = father;
		for (int i = 1; i <= 20; i++)
			fa[u][i] = fa[fa[u][i - 1]][i - 1];

		for (auto v : e[u])
			if (v != father)
				dfs(v, u, e);
	}

	int lca(int u, int v) {
		if (dep[u] < dep[v])
			swap(u, v);
		for (int i = 20; i >= 0; i--)
			if (dep[fa[u][i]] >= dep[v])
				u = fa[u][i];
		if (u == v)
			return v;
		for (int i = 20; i >= 0; i--)
			if (fa[u][i] != fa[v][i])
				u = fa[u][i], v = fa[v][i];
		return fa[u][0];
	}
};
vector<int>dep;
vector<vector<int>> e;
vector<int>nd1, nd2;//记录直径的两个端点
vector<int>len;//存点x的子树的直径长度
lca_st lca;
int get(int a, int b) {//求两点距离
	return lca.dep[a] + lca.dep[b] - 2 * lca.dep[lca.lca(a, b)];
}
void dfs(int x, int fa) {
	nd1[x] = nd2[x] = x;
	dep[x] = dep[fa] + 1;

	//动态维护x子树的直径
	//每次做两棵树的直径合并操作
	for (auto a : e[x]) {
		if (a == fa)	continue;
		dfs(a, x);
		vector<int>ends = { nd1[x],nd2[x],nd1[a],nd2[a] };
		for (int i = 0; i < 4; i++) {
			for (int j = i + 1; j < 4; j++) {
				int dist = get(ends[i], ends[j]);
				if (dist > len[x]) {
					len[x] = dist;
					nd1[x] = ends[i];
					nd2[x] = ends[j];
				}
			}
		}
	}
}
void solve() {

	int n; cin >> n;
	len = nd1 = nd2 = dep = vector<int>(n + 1);
	e = vector<vector<int>>(n + 1);

	for (int i = 1; i < n; i++) {
		int a, b; cin >> a >> b;
		e[a].push_back(b);
		e[b].push_back(a);
	}

	lca.init(n);
	lca.dfs(1, 0, e);
	dep[0] = -1;
	dfs(1, 0);

	int q; cin >> q;
	vector<int>rs;
	while (q--) {
		int v, k;
		cin >> v >> k;
		int ret = k;
		int now = v;
		int tip = 0;
		if (k >= dep[v]) {
			now = 1;
		}
		else {
			while (k) {
				if (k & 1) {
					now = lca.fa[now][tip];
				}
				k >>= 1;
				tip++;
			}
		}

		int len1 = get(nd1[now], v);
		int len2 = get(nd2[now], v);
		rs.push_back(max({ len1, len2 ,min(dep[v],ret) }));
	}
	for (auto a : rs)
		cout << a << ' ';

	cout << endl;

}
signed main() {

	std::ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	ll T;
	T = 1;

	cin >> T;
	while (T--)
		solve();

	return 0;
}

Logo

集算法之大成!助力oier实现梦想!

更多推荐