题意
给定一棵无向树,m次标记点,标记点的同时询问从根出发访问所有点的最短路径和。走到最后一个点不用返回。
思路
逆向思维,考虑加入一个点的时候,可以走的最优方案。
对于一个新加入的点,如果可以从计划访问的点,走到这个新的点,那么是较优的选择。
考虑从新加入的点逐级回溯,如果可以到达某个计划访问的点,那么从该点出发到这个新加入的点是最优方案。
由于走到最后一个点不用返回,所以方案调整为:走到最深的点不再返回。所以dfs记录深度,同时维护当前最大深度即可。
solution
#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l; i <= r; ++i)
using namespace std;
typedef long long ll;
#define int ll
typedef vector<int> vi;
const int N = 1e5 + 7;
bool vis[N]; // 记录访问标记
int fa[N]; // 记录父节点
int go(int x) {
int ans = 0;
while (!vis[x]) { // 向上回溯,直到找到遍历过的点为止
vis[x] = 1;
ans += 2;
x = fa[x]; // 路上加入访问标记
}
return ans;
}
vector<int> G[N];
int d[N];
void dfs(int fa, int dep = 0) { // 遍历树,标记深度
d[fa] = dep;
for (int x : G[fa]) {
dfs(x, dep + 1);
}
}
signed main() {
int n, m, rt;
cin >> n >> m;
rep(i, 1, n) {
cin >> fa[i];
if (fa[i] == -1) // 标记根节点
rt = i;
else
G[fa[i]].push_back(i); // 建树
}
vis[rt] = 1; // 初始时根已访问
dfs(rt);
int x, maxd = 0, ans = 0;
while (m--) {
cin >> x;
ans += go(x);
maxd = max(maxd, d[x]); // 更新最大深度
cout << ans - maxd; // 最大深度的那个点最后走
if (m) cout << '\n';
}
return 0;
}