花卉港湾 (floral)
题目来源:\(2025\) 年省选集训第二轮 Day 1。
\(\S 1\quad\) 题意
给定一棵 \(n\) 个点的树,第 \(i\) 条边连接 \(u_i\) 和 \(v_i\)。令 \(G=(V,E)\),其中 \(V=\set{1,2,\dots,n}\),\(E=\set{(u,v)\mid \text{dist}(u,v)\ge K}\)。
接下来有 \(Q\) 次询问,每次询问给定两个点 \(u,v\),输出图 \(G\) 上 \(u\) 和 \(v\) 之间的最短路。
| \(\text{Subtask}\) | \(n\) | \(Q\) | 特殊性质 | 分值 |
|---|---|---|---|---|
| \(1\) | \(\le 200\) | \(\le 200\) | \(30\) | |
| \(2\) | \(\le 2000\) | \(\le 2000\) | \(25\) | |
| \(3\) | \(\le 10^5\) | \(\le 10^5\) | 树的第 \(i\) 条边为 \((i,i+1)\) | \(20\) |
| \(4\) | \(\le 10^5\) | \(\le 10^5\) | \(25\) |
\(\S 2\quad\) 思路
性质: 答案至多为 \(3\)。
证明: 考虑对于任意一个点 \(u\),其最远点必然在树的某条直径上,而直径的长度必然大于等于 \(K\),否则无解。令 \(u\) 的最远点为 \(F_u\),\(v\) 的最远点为 \(F_v\),则分为两种情况:
-
\(F_u\) 和 \(F_v\) 之间的距离 \(\ge K\):那么显然从 \(u\to F_u\to F_v\to v\) 便是一条可行的路径。
-
\(\text{otherwise}\):\(F_u\) 和 \(F_v\) 所处直径相交的点数必然大于 \(1\)(即除去所有直径相交的点之外,还有其余点相交)
但是,这种情况 \(F_u\) 必然可以直接到 \(v\)。因为 \(F_u\) 到 \(\text{LCA}(F_u,F_v)\) 的距离与 \(F_v\) 到 \(\text{LCA}(F_u,F_v)\) 的距离是相等的,因为如果不相等,则 \(F_u\) 或 \(F_v\) 必然有一个不是最远点。
推论: \(u\) 和 \(v\) 不联通当且仅当 \(u\) 到其最远点的距离小于 \(K\) 或 \(v\) 到其最远点的距离小于 \(K\)。
证明: 根据刚才的证明,发现过程中仅用到了 \(u\) 到其最远点的距离 \(\ge K\) 以及 \(v\) 到其最远点的距离 \(\ge K\)(若满足上述两个条件,直径必然 \(\ge K\))。
有了上述两个结论之后,我们发现只需要再判定答案是 \(1\) 还是 \(2\) 了。\(1\) 很简单,当且仅当 \(\text{dist}(u,v)\ge K\),所以只有 \(2\) 是困难的。接下来,讨论若干种解决 \(2\) 的做法。
\(\S 2.1\quad\) 思路 1
笔者赛时思路,但是赛时并未调出来,赛后调出来的。
长度是 \(2\) 相当于 \(u\to w\to v\),而我们并不能暴力地枚举 \(w\),这样只能过 \(\text{Subtask}\) \(1,2\)。将 \(w\) 可能的取值划分为 \(5\) 的部分:
-
\(\text{Part 1}\):距离 \(\text{lca}\) 最远的节点 \(w\),便是最优的。判断条件为:
- \(\text{dist}(u,w)\ge K\) 且 \(\text{dist}(v,w)\ge K\)
-
\(\text{Part 2/3}\):以 \(\text{Part 2}\) 为例,\(\text{Part 3}\) 同理。令 \(i\) 为 \(\text{lca}(u,w)\),那么当 \(i\) 固定的情况下,\(w\) 必然选择 \(i\) 子树内(排出 \(u\) 所对应的子树)最远的点,这个是可以预处理的,记作 \(mx_i\)。列出条件:
- \(\text{dep}_u-\text{dep}_w+mx_w\ge K\)
- \(\text{dep}_v-\text{dep}_{\text{lca}}+mx_w+\text{dep}_{w}\ge K\)
化简一下:\(mx_w\ge \max(K-\text{dep}_u+\text{dep}_w,K-\text{dep}_v+\text{dep}_{\text{lca}}-\text{dep}_w)\)。
此时,分讨 \(\text{max}\) 的取值,发现在左侧是一段连续的区间,取值在右侧是一段连续的区间。因为
\[\begin{align} K-\text{dep}_u+\text{dep}_w&\ge K - \text{dep}_v+\text{dep}_\text{lca}-\text{dep}_w\\ 2\text{dep}_w&\ge \text{dep}_u-\text{dep}_v+\text{dep}_{\text{lca}} \end{align} \]这显然在一条链上是连续的。
于是,转化为:
\[mx_w-\text{dep}_w\ge K-\text{dep}_u \]等价于求解路径最大值。由于没有修改,故采用倍增即可。
-
\(\text{Part 4/5}\):只需要求解子树内最长链,并判断是否大于等于 \(K\) 即可。
时间复杂度:\(O(n\log n)\)。
$\S 2.2\quad $ 思路 2
既然直径可找出 \(3\) 的通解,那能不能也在直径上进行判断是否存在 \(2\) 的答案呢?
考虑在一条直径上,每个点都会拉出去若干棵子树,考虑每个点到直径上的点拉出了一条链(令 \(top_u\) 表示 \(u\) 点不断走父亲边到达的直径上的点):
接下来,考虑 \(w\) 的位置:
- 如果在 \(top_u/top_v\) 连接的子树中,则还是不如直接在直径端点,因为比如说 \(w\) 在 \(top_u\) 中,那么 \(\text{dist}(u,w)<\text{dist}(a,w)\)。而且 \(\text{dist}(v,w)=\text{dist}(a,v)\),所以说 \(w\) 一定不如 \(a\)。
于是,只需要考虑 \(w\) 在直径上某个点 \(i\) 拉出的子树内。列出条件:
- \(mx_i+i-top_u+\text{dep}_u\ge K\)
- \(mx_i+top_v-i+\text{dep}_u\ge K\)
这是一个二维偏序,过于复杂。但是,由于只需要判断合法性,所以一维直接求最大值即可,转变为全序!
如果使用 \(O(n)-O(1)\) LCA,即可做到时间复杂度 \(O(n)\)。线性!
\(\S 3\quad\) 代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100005;
int N, K, Q;
std::vector<int> g[maxn];
int dep[maxn];
int dmx[maxn][3], umx[maxn];
int fa[maxn][20], sub[maxn][20], sum[maxn][20];
void dfs1(int u) {
dmx[u][0] = 0;
for (int i = 1; i < 20; i ++)
fa[u][i] = fa[fa[u][i - 1]][i - 1];
for (auto v : g[u]) {
if (v == fa[u][0]) continue;
dep[v] = dep[u] + 1, fa[v][0] = u;
dfs1(v);
if (dmx[v][0] + 1 > dmx[u][0]) {
dmx[u][2] = dmx[u][1];
dmx[u][1] = dmx[u][0];
dmx[u][0] = dmx[v][0] + 1;
} else if (dmx[v][0] + 1 > dmx[u][1]) {
dmx[u][2] = dmx[u][1];
dmx[u][1] = dmx[v][0] + 1;
} else if (dmx[v][0] + 1 > dmx[u][2]) {
dmx[u][2] = dmx[v][0] + 1;
}
}
}
void dfs2(int u) {
if (dmx[u][0] + 1 == dmx[fa[u][0]][0]) {
sub[u][0] = K - dmx[fa[u][0]][1] - dep[fa[u][0]];
sum[u][0] = K - dmx[fa[u][0]][1] + dep[fa[u][0]];
} else {
sub[u][0] = K - dmx[fa[u][0]][0] - dep[fa[u][0]];
sum[u][0] = K - dmx[fa[u][0]][0] + dep[fa[u][0]];
}
for (int i = 1; i < 20; i ++) {
sub[u][i] = min(sub[u][i - 1], sub[fa[u][i - 1]][i - 1]);
sum[u][i] = min(sum[u][i - 1], sum[fa[u][i - 1]][i - 1]);
}
int mx1 = -(1 << 30), mx2 = -(1 << 30);
for (auto v : g[u]) {
if (v == fa[u][0]) continue;
if (dmx[v][0] > mx1) mx2 = mx1, mx1 = dmx[v][0];
else if (dmx[v][0] > mx2) mx2 = dmx[v][0];
}
for (auto v : g[u]) {
if (v == fa[u][0]) continue;
umx[v] = umx[u] + 1;
if (mx1 == dmx[v][0]) umx[v] = max(mx2 + 2, umx[v]);
else umx[v] = max(mx1 + 2, umx[v]);
dfs2(v);
}
}
int lca(int a, int b) {
if (dep[a] < dep[b]) swap(a, b);
for (int i = 19; i >= 0; i --)
if (dep[fa[a][i]] >= dep[b]) {
a = fa[a][i];
}
if (a == b) return a;
for (int i = 19; i >= 0; i --)
if (fa[a][i] != fa[b][i]) {
a = fa[a][i];
b = fa[b][i];
}
return fa[a][0];
}
int main() {
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(0);
cin >> N >> Q >> K;
for (int i = 1; i < N; i ++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
memset(sum, 0x3f, sizeof sum);
memset(sub, 0x3f, sizeof sub);
memset(dmx, -0x3f, sizeof dmx);
dep[1] = 1, dfs1(1), dfs2(1);
while (Q -- ) {
int u, v;
cin >> u >> v;
if (min(max(umx[u], dmx[u][0]), max(umx[v], dmx[v][0])) < K)
cout << -1 << endl;
else {
int LCA = lca(u, v);
if (dep[u] + dep[v] - 2 * dep[LCA] >= K)
cout << 1 << endl;
else if (umx[LCA] >= max(K - dep[u] + dep[LCA], K - dep[v] + dep[LCA]))
cout << 2 << endl;
else {
bool ok = false;
int P[2] = {0};
for (int t : {0, 1}) {
int x = u, res = 1 << 30;
if (u == LCA) {
swap(u, v);
continue;
}
for (int i = 19; i >= 0; i --) {
int j = fa[fa[x][i]][0];
if (dep[fa[x][i]] > dep[LCA] + 1 && dep[u] - dep[j] <= dep[v] + dep[j] - 2 * dep[LCA]) {
res = min(res, sum[x][i]);
x = fa[x][i];
}
}
if (dep[x] > dep[LCA] + 1 && dep[u] - dep[x] <= dep[v] + dep[x] - 2 * dep[LCA])
res = min(res, sum[x][0]);
if (res <= dep[u]) ok = true;
res = 1 << 30;
if (dep[x] > dep[LCA] + 1) {
x = fa[x][0];
for (int i = 19; i >= 0; i -- ) {
if (dep[fa[x][i]] > dep[LCA] + 1) {
res = min(res, sub[x][i]);
x = fa[x][i];
}
}
if (dep[x] > dep[LCA] + 1) res = min(res, sub[x][0]);
if (res <= dep[v] - 2 * dep[LCA]) ok = true;
}
if (fa[x][0] == LCA) P[t] = x;
else P[t] = fa[x][0];
assert(fa[P[t]][0] == LCA);
swap(u, v);
}
int temp[2] = {dep[P[0]] - dep[LCA] + dmx[P[0]][0], dep[P[1]] - dep[LCA] + dmx[P[1]][0]};
if (u == LCA) temp[0] = -1;
if (v == LCA) temp[1] = -1;
if (temp[0] == dmx[LCA][0] && temp[1] == dmx[LCA][1] || temp[0] == dmx[LCA][1] && temp[1] == dmx[LCA][0])
ok |= dmx[LCA][2] + min(dep[u] - dep[LCA], dep[v] - dep[LCA]) >= K;
else if (dmx[LCA][0] == temp[0] || dmx[LCA][0] == temp[1])
ok |= dmx[LCA][1] + min(dep[u] - dep[LCA], dep[v] - dep[LCA]) >= K;
else
ok |= dmx[LCA][0] + min(dep[u] - dep[LCA], dep[v] - dep[LCA]) >= K;
if ((u != LCA && dmx[u][0] >= K) || (v != LCA && dmx[v][0] >= K)) ok = true;
if (ok) cout << 2 << endl;
else cout << 3 << endl;
}
}
}
return 0;
}

浙公网安备 33010602011771号