题解 | 计树
计树
https://www.nowcoder.com/practice/e2b6744ec3f94f81aad1c6b8a372b5eb
#include <iostream> #include <vector> #include <queue> #include <algorithm> using namespace std; const int MAXN = 1e5 + 5; // 函数:构建子节点列表和父节点数组 void buildChildrenAndParent(int n, const vector<vector<int>>& edges, vector<int>& parent, vector<vector<int>>& children) { queue<int> q; q.push(1); // 从根节点1开始 parent[1] = 0; // 根节点的父节点是0(虚拟节点) while (!q.empty()) { int u = q.front(); // 当前节点 q.pop(); for (int v : edges[u]) { // 遍历当前节点的所有邻接点 if (v != parent[u]) { // 如果邻接点不是当前节点的父节点 parent[v] = u; // 设置当前节点为邻接点的父节点 children[u].push_back(v); // 将邻接点添加到当前节点的子节点列表中 q.push(v); // 将邻接点加入队列,以便后续处理 } } } } // 函数:计算 s 数组 void calculateSArray(int n, const vector<vector<int>>& children, const vector<int>& is_in_V, vector<long long>& s) { vector<pair<int, bool>> stack; // 用于深度优先搜索(DFS)的栈 stack.emplace_back(1, false); // 从根节点1开始,标记为未访问 while (!stack.empty()) { auto [node, visited] = stack.back(); // 获取栈顶元素 stack.pop_back(); if (!visited) { // 如果当前节点未访问 stack.emplace_back(node, true); // 标记为已访问 // 逆序压入子节点以保证处理顺序正确 for (auto it = children[node].rbegin(); it != children[node].rend(); ++it) { stack.emplace_back(*it, false); // 将子节点压入栈 } } else { // 如果当前节点已访问 long long s_val = is_in_V[node]; // 当前节点的s值初始化为是否在集合V中 for (int child : children[node]) { // 遍历当前节点的子节点 s_val += s[child]; // 累加子节点的s值 } s[node] = s_val; // 更新当前节点的s值 } } } // 函数:计算 cnt 数组 void calculateCntArray(int n, const vector<vector<int>>& children, const vector<long long>& s, vector<long long>& cnt) { for (int i = 1; i <= n; ++i) { // 遍历所有节点 long long total = s[i] * s[i]; // 当前节点的s值的平方,表示所有可能的节点对 long long sum_child = 0; // 子节点的s值的平方和 for (int child : children[i]) { // 遍历当前节点的子节点 sum_child += s[child] * s[child]; // 累加子节点的s值的平方 } cnt[i] = total - sum_child; // 当前节点作为LCA的次数等于总对数减去子节点的对数 } } int main() { ios::sync_with_stdio(false); // 加快输入输出速度 cin.tie(nullptr); int n; // 树的节点数 cin >> n; vector<vector<int>> edges(n + 1); // 用邻接表表示树的边 for (int i = 0; i < n - 1; ++i) { int u, v; // 读取一条边的两个节点 cin >> u >> v; edges[u].push_back(v); // 添加边 edges[v].push_back(u); } int k; // 集合V的大小 cin >> k; vector<int> V(k); // 集合V中的节点 for (int i = 0; i < k; ++i) { cin >> V[i]; // 读取集合V中的节点 } // 标记是否在集合 V 中 vector<int> is_in_V(n + 1, 0); // 标记数组,记录每个节点是否在集合V中 for (int node : V) { is_in_V[node] = 1; // 如果节点在集合V中,标记为1 } vector<int> parent(n + 1, 0); // 存储每个节点的父节点 vector<vector<int>> children(n + 1); // 存储每个节点的子节点 buildChildrenAndParent(n, edges, parent, children); // 构建子节点列表和父节点数组 vector<long long> s(n + 1, 0); // s[i] 表示以节点i为根的子树中,集合V中的节点数 calculateSArray(n, children, is_in_V, s); // 计算 s 数组 vector<long long> cnt(n + 1, 0); // cnt[i] 表示节点i作为LCA的次数 calculateCntArray(n, children, s, cnt); // 计算 cnt 数组 // 输出结果 for (int i = 1; i <= n; ++i) { cout << cnt[i] << ' '; // 输出每个节点作为LCA的次数 } cout << endl; return 0; }#春招##春招笔试训练营##春招刷题训练营#