题目
输入样例:
7 4
-1 1 1 1 2 2 3
5
6
2
4
输出样例:
2
4
4
6
思路
看题目首先知道要把树给建起来,直接按照题目给出的信息建出来的树是很可能是反向的 ,至少我一开始反向的 。 但是为了便于求每个节点的深度,我们需要再建一个正向的树。反向的树由于每个节点对应的父节点只有一个,所以我选择用哈希表来存;正向的树每个父节点对应两个叶子结点,所以我选择了链式前向星来存。
题目要求的结果是什么呢?其实就是当前所有加入的节点连通根节点构成的树上的所有的边的数目*2-最深的节点的深度。这个其实是一个简单的贪心思想,根据题意我们知道,龙龙每次送完外卖再去送下一个地方时,需要先移动到这两个地方的最近公共祖先,那么当前节点到最近公共祖先这一条路就走了两次,由于龙龙送完最后一趟外卖后不需要回到根节点,那么要使龙龙走的总距离最短,我们只需要让他深度最深的那一条路只走一次就行了(这个结论虽然在pta上验证是正确的,但是我还没有想到严格的证明,希望有想法的小伙伴和我交流一下)
AC代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
struct edge {
int to, next;
} edges[maxn];
int head[maxn];
int tot = -1;
bool vis[maxn];
int root;
void add(int u, int v) {
if(u < 0) {root = v; return;} // 处理根节点
++tot;
edges[tot].to = v;
edges[tot].next = head[u];
head[u] = tot;
}
unordered_map<int, int> tree;
int height[maxn];
void pre(int cur, int de) { // 前序遍历求每个节点的深度
if(cur != -1) {
height[cur] = de;
for(int i = head[cur]; i != -1; i = edges[i].next) {
pre(edges[i].to, de+1);
}
}
}
int main(void)
{
freopen("in.txt", "r", stdin);
memset(head, -1, sizeof(head));
memset(vis, 0, sizeof(vis));
int N, M;
scanf("%d%d", &N, &M);
int u;
for(int i = 1; i <= N; ++i) {
scanf("%d", &u);
add(u, i); // 添加正向边
tree[i] = u; // 添加反向边
}
pre(root, 0);
int totedge = 0; // 当前生成树的总边树
int maxdepth = 0; // 最深的深度
bool flag = true;
for(int i = 0; i < M; ++i) {
scanf("%d", &u);
maxdepth = max(maxdepth, height[u]);
while(tree[u] != -1 && !vis[u]) {
vis[u] = true;
++totedge;
u = tree[u];
}
if(flag) flag = false;
else printf("\n");
printf("%d", totedge*2-maxdepth);
}
return 0;
}