题解:P10105 [GDKOI2023 提高组] 游戏

题意

给定一颗 \(n\) 个点的树,\(q\) 次询问 \(x,y,z\),找到 \(3\) 个点 \(u,v,w\) 满足 \(\operatorname{dist}(u,v)=x,\operatorname{dist}(u,w)=y,\operatorname{dist}(v,w)=z\),数据保证有解。\(1\leq n,q\leq 2\times 10^5\)

题解

首先有经典结论,三点两两间的路径恰好交于一点 \(p\),且三点位于点 \(p\) 的不同子树中。我们容易列出关于 \(\operatorname{dist}(p,u),\operatorname{dist}(p,v),\operatorname{dist}(p,w)\) 的方程:

\[\begin{cases} \operatorname{dist}(p,u)+\operatorname{dist}(p,v)=x\\ \operatorname{dist}(p,u)+\operatorname{dist}(p,w)=y\\ \operatorname{dist}(p,v)+\operatorname{dist}(p,w)=z \end{cases} \]

解得

\[\begin{cases} \operatorname{dist}(p,u)=\frac{x+y-z}{2}\\ \operatorname{dist}(p,v)=\frac{x-y+z}{2}\\ \operatorname{dist}(p,w)=\frac{-x+y+z}{2}\\ \end{cases} \]

考虑枚举这个点 \(p\) 后怎么做:我们需要 \(u,v,w\) 放到 \(p\) 的不同的子树里,那直接贪心,将距离 \(p\) 最大的点放到以 \(p\) 为根深度最大的子树中,距离次大、次次大同理。

于是我们对于每个点,预处理出以它为根,深度最大/次大/次次大的子树深度,这个可以树形 DP 做。令 \(f_{u,0/1/2}\) 表示以 \(u\) 为根时深度最大/次大/次次大的子树深度,\(g_{u,0/1/2}\) 表示对应的叶子,\(b_{u,0/1/2}\) 表示是从哪个子树转移过来的。初值令 \(f_{u,0}\leftarrow 0,g_{u,0}\leftarrow u,b_{u,0}\leftarrow u\),对于 \(i=1/2\)\(f\)\(f_{u,i}\leftarrow -\infty\)。转移相当于每次把 \(f_{u,0/1/2}\)\(f_{v,0}\) 放在一起排序,取前 \(3\) 大,写一个类似冒泡排序的东西就行了。然后做换根 DP,注意 \(v\) 从父亲节点 \(u\) 处的子树转移时,要保证 \(b_{u,i}\neq v\),转移是类似的。

那么现在我们需要找到一个满足要求的点 \(p\),满足 \(f_{p,0}\geq a,f_{p,1}\geq b,f_{p,2}\geq c\),看上去是三维偏序,但是我们可以对 \(f_{p,2}\) 这一维直接贪心,变成找到一个点 \(p\),满足 \(f_{p,0}\geq a,f_{p,1}\geq b\)\(f_{p,2}\) 最大,这样就是二维偏序了,扫描线,用树状数组维护后缀 \(\max\)\(\operatorname{argmax}\) 即可。

最后就是找到点 \(p\) 后,需要找出对应的 \(x,y,z\),相当于要查询从 \(x\) 开始,沿着 \(\text{path}(x,y)\)\(k\) 步到达的点。这是简单的,倍增预处理树上 \(2^k\) 级祖先,然后做简单的分类讨论即可。注意我们会把 \(\operatorname{dist}(p,u),\operatorname{dist}(p,v),\operatorname{dist}(p,w)\) 排序,计算答案时需要恢复原来的顺序。

\(n,q\) 同阶,时间复杂度为 \(\mathcal{O}(n\log{n})\)

代码

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

#define lowbit(x) ((x) & -(x))
#define chk_min(x, v) (x) = min((x), (v))
#define chk_max(x, v) (x) = max((x), (v))
typedef long long ll;
typedef pair<int, int> pii;
const int N = 2e5 + 5, LGN = 18 + 5, INF = 1e9;

int n, q, f[N][3], g[N][3], b[N][3], ans[N];
int lg2[N], dep[N], fa[LGN][N];
int sz;

struct Rec { int id, val, ans; };
struct Pt {
	int id, x, y, z;
	bool operator<(const Pt &p) const { return x > p.x; }
} p[N];
struct Query {
	int id, x, y, z;
	bool operator<(const Query &q) const { return x > q.x; }
} qr[N], tq[N];
struct AdjList {
	int tot, head[N], nxt[N << 1], to[N << 1];
	void init() { tot = 0, memset(head + 1, -1, n << 2); }
	void ins(int x, int y) { to[++tot] = y, nxt[tot] = head[x], head[x] = tot; }
} t;
struct BIT {
	pii c[N];
	void init() { for (int i = 0; i <= n + 1; ++i) c[i] = { -INF, -INF }; }
	pii query(int x) {
		pii res = { -INF, 0 };
		for (; x <= n + 1; x += lowbit(x)) chk_max(res, c[x]);
		return res;
	}
	void change(int x, pii v) { for (; x; x -= lowbit(x)) chk_max(c[x], v); }
} ft;

bool cmp1(Rec x, Rec y) { return x.val > y.val; }
bool cmp2(Rec x, Rec y) { return x.id < y.id; }

void add(int x, int val, int lf, int br) {
	if (val < f[x][2]) return;
	f[x][2] = val, g[x][2] = lf, b[x][2] = br;
	for (int i = 2; i; --i) if (f[x][i] > f[x][i - 1])
		swap(f[x][i], f[x][i - 1]), swap(g[x][i], g[x][i - 1]), swap(b[x][i], b[x][i - 1]);
}
void dfs1(int x) {
	for (int i = 1; i <= lg2[dep[x]]; ++i) fa[i][x] = fa[i - 1][fa[i - 1][x]];
	f[x][0] = 0, g[x][0] = b[x][0] = x, f[x][1] = f[x][2] = -INF;
	for (int i = t.head[x]; ~i; i = t.nxt[i]) {
		int y = t.to[i];
		if (y == fa[0][x]) continue;
		fa[0][y] = x, dep[y] = dep[x] + 1, dfs1(y);
		add(x, f[y][0] + 1, g[y][0], y);
	}
}
void dfs2(int x) {
	for (int i = t.head[x]; ~i; i = t.nxt[i]) {
		int y = t.to[i];
		if (y == fa[0][x]) continue;
		for (int j = 0; j < 3 && f[x][j] != INF; ++j)
			if (b[x][j] != y) { add(y, f[x][j] + 1, g[x][j], x); break; }
		dfs2(y);
	}
}
int lca(int x, int y) {
	if (dep[x] > dep[y]) swap(x, y);
	for (int i = lg2[dep[y] - dep[x]]; ~i; --i)
		if (dep[fa[i][y]] >= dep[x]) y = fa[i][y];
	if (x == y) return x;
	for (int i = lg2[dep[x]]; ~i; --i)
		if (fa[i][x] ^ fa[i][y]) x = fa[i][x], y = fa[i][y];
	return fa[0][x];
}
int kth_anc(int x, int k) {
	for (int i = lg2[k]; ~i; --i)
		if (k >> i & 1) x = fa[i][x];
	return x;
}
int kth(int x, int y, int k) {
	int d = lca(x, y);
	if (y == d) swap(x, y), k = dep[y] - dep[x] - k;
	if (dep[y] - dep[d] >= k) return kth_anc(y, k);
	k -= dep[y] - dep[d];
	return kth_anc(x, dep[x] - dep[d] - k);
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> n, t.init(), ft.init();
    for (int i = 1, u, v; i < n; ++i) cin >> u >> v, t.ins(u, v), t.ins(v, u);
    for (int i = 2; i <= n; ++i) lg2[i] = lg2[i >> 1] + 1;
    dep[1] = 1, dfs1(1), dfs2(1);
    for (int i = 1; i <= n; ++i)
    	if (f[i][1] >= 0) p[++sz] = { i, f[i][0], f[i][1], f[i][2] };
    cin >> q;
    for (int i = 1, u, v, w; i <= q; ++i) {
    	static int val[3];
    	cin >> u >> v >> w;
    	val[0] = (u + v - w) >> 1, val[1] = (u - v + w) >> 1, val[2] = (-u + v + w) >> 1;
    	tq[i] = { i, val[0], val[1], val[2] };
    	sort(val, val + 3);
    	qr[i] = { i, val[2], val[1], 0 };
    }
    sort(p + 1, p + sz + 1), sort(qr + 1, qr + q + 1);
    for (int x = n, i = 1, j = 1; ~x; --x) {
    	while (i <= sz && p[i].x == x) ft.change(p[i].y + 1, { p[i].z, p[i].id }), ++i;
     	while (j <= q && qr[j].x == x) ans[qr[j].id] = ft.query(qr[j].y + 1).second, ++j;
    }
    for (int i = 1; i <= q; ++i) {
    	static Rec val[3];
    	int p = ans[i];
    	val[0] = { 0, tq[i].x }, val[1] = { 1, tq[i].y }, val[2] = { 2, tq[i].z };
    	sort(val, val + 3, cmp1);
    	for (int j = 0; j < 3; ++j) val[j].ans = kth(g[p][j], p, val[j].val);
    	sort(val, val + 3, cmp2);
    	for (int j = 0; j < 3; ++j) cout << val[j].ans << " \n"[j == 2];
    }
}
posted @ 2025-04-12 18:25  P2441M  阅读(35)  评论(0)    收藏  举报