题解 | 计树

计树

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;
}

#春招##春招笔试训练营##春招刷题训练营#
全部评论

相关推荐

迟缓的斜杠青年巴比Q了:简历被投过的公司卖出去了,我前两天遇到过更离谱的,打电话来问我有没有意向报班学Java学习,服了,还拿我学校一个学长在他们那报班学了之后干了华为OD当招牌
点赞 评论 收藏
分享
不愿透露姓名的神秘牛友
06-18 17:37
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务