CF1675F 题解
这道题其实不难,CF 给它估的 1800 1800 1800,过高了。
解法
贪心。一个重要的结论:同一子树内的节点必须连续访问。
证明:
- 假设先访问其它节点的子树。
- 设 d i d_i di 为节点 i i i 的深度,设 lca ( u , v ) \text{lca}(u,v) lca(u,v) 为节点 u u u 和 v v v 的 LCA。
- 不妨考虑连续三个节点的距离总和,设它们为 x , y , z x,y,z x,y,z。
- 不难得到,距离总和为 d x + 2 d y − 2 d lca ( x , y ) − 2 d lca ( y , z ) d_{x}+2d_{y} - 2d_{\text{lca}(x,y)}-2d_{\text{lca}(y,z)} dx+2dy−2dlca(x,y)−2dlca(y,z)。
- 再看如果子树内连续,距离总和也为此,但明显前者的 lca \text{lca} lca 比后者的要浅。
- 所以,连续的时间更少。
我们进行两次 DFS,第一次记录每个子树的被标记的总和和它们的深度,父节点。
第二次记录每个子树的路径和。显然,没有标记的子树是不需要访问的。
最后一点在于,终点是 y y y,不是 x x x。
于是,我们需要将 y y y 往上走,暴力找 lca \text{lca} lca,但是只要求一次,不需要倍增。
复杂度 O ( n ) O(n) O(n)。
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
bool a[200001];
int s[200001], d[200001], e[200001], v[200001];
vector<int> gv[200001];
void add_edge(int u, int v){
gv[u].push_back(v);
gv[v].push_back(u);
}
void dfs(int n, int fa){
if(fa == -1){
v[n] = -1;
}
s[n] = a[n];
for(auto i : gv[n]){
if(i == fa){
continue;
}
e[i] = e[n] + 1;
v[i] = n;
dfs(i, n);
s[n] += s[i];
}
}
void dfs1(int n, int fa){
for(auto i : gv[n]){
if(i == fa || !s[i]){
continue;
}
dfs1(i, n);
d[n] += d[i] + 2;
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t;
cin >> t;
while(t--){
int n, k;
cin >> n >> k;
int x, y;
cin >> x >> y;
memset(a, 0, sizeof(a));
memset(d, 0, sizeof(d));
e[x] = 0;
for(int i = 0;i <= n;i++){
gv[i].clear();
}
for(int i = 0;i < k;i++){
int p;
cin >> p;
a[p] = 1;
}
for(int i = 0;i < n - 1;i++){
int u, v;
cin >> u >> v;
add_edge(u, v);
}
dfs(x, -1);
dfs1(x, -1);
int i = y, kk = 0;
while(!s[i]){
i = v[i];
kk++;
}
cout << d[x] - (e[y] - (kk * 2)) << endl;
}
return 0;
}