題面
現在的題似乎都找不到原題了
掛個 pdf
題面下載
演算法
容易想到鏈和菊花圖的做法, 需要注意的是計算深度只能用 \(\rm{dfs}\) 來跑, 不能保證鏈的順序與輸入順序相同
對於 \(n, m \leq 10^3\), 觀察暴力做法
暴力
容易發現對於每一個點, 都要由起點 \(1\) 開始, 先到達一條鏈上最深的點, 再折返跑過來
對於每一個 \(a_i, i \in [1, m]\) , 將 \(a_1 \sim a_i\) 打上標記, \(\rm{dfs}\) 搜尋每一個點, 判斷這個點下方是否還有有標記的點, 若沒有統計這個點的深度, 減去起點深度之後乘以二, 最後減去終點到起點即可
暴力程式碼
int dfs2(int u=1,int fa=0){
int ret=0;
for(int v:e[u]){
if(v==fa)continue;
ret+=dfs2(v,u);
}
if(ret==0)return 2*usd[u];
else return ret+2;
}
暴力總結
\(\rm{dfs}\) 的遞迴特性使其能判斷自己下方的情況
若從起點開始找終點困難, 考慮從每個終點拆分的去計算路徑長度
樹上的距離問題, 通常可以拆分成樹上深度問題
正解
觀察到暴力方式每一次加入新點都需要重新跑一邊 \(\rm{dfs}\), 這顯然是無必要的
如果能每次加入一個點, 計算這個點帶來的路徑變化即可
想到 LCA, 然而需要和前面每一個點求 LCA , 對時間複雜度最佳化不大 ( \(n, m\) 為同一數級 )
觀察圖的性質, 發現在 LCA 之後的路徑都已經被前面的 \(\rm{dfs}\) 計算過了, 於是在每次 \(\rm{dfs}\) 計算時, 把經過的每一條邊都標記起來, 如果遇到已經標記的邊, 則無需計算
正解程式碼
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 100010;
int n, m, a[maxn], fa[maxn], dep[maxn], ans, vis[maxn];
vector<int> vec[maxn];
void dfs(int u, int f)
{
fa[u] = f;
dep[u] = dep[f] + 1;
for (auto v : vec[u])
if (v != f)
{
dfs(v, u);
}
}
void add(int u)
{
if (vis[u])
return;
int res = 1;
vis[u] = 1;
while (!vis[fa[u]])
u = fa[u], vis[u] = 1, res++;
ans += 2 * res;
}
signed main()
{
// freopen ("B.in", "r", stdin);
// freopen ("B.out", "w", stdout);
scanf("%lld%lld", &n, &m);
for (int i = 1, u, v; i < n; i++)
{
scanf("%lld%lld", &u, &v);
vec[u].push_back(v);
vec[v].push_back(u);
}
for (int i = 1; i <= m; i++)
{
scanf("%lld", &a[i]);
}
dep[0] = -1;
dfs(1, 0);
vis[1] = 1;
for (int i = 1; i <= m; i++)
{
add(a[i]);
printf("%lld\n", ans - dep[a[i]]);
}
return 0;
}
正解總結
想到複雜的方法, 最佳化不了複雜度
考慮找規律找更簡單的方法