P3884 [JLOI2009]二叉树问题

题目传送门

一、理解与感悟

1、树结点需要由父到子,有时也需要由子到父,所以结构体需要修改如下:

//树的结构体+存储数组
struct Node {
    int id;     // 当前结点ID
    int father; // 爸爸
    int left;   // 左结点ID
    int right;  // 右结点ID
    int depth;  // 深度,不用再次dfs去探测
} t[N];

2、题目只说了表示从结点x到结点y(约定根结点为1),这样可不一定是从父到子,也可能是从子到父,需要两头看一下。

3、建图:如果出题人使坏,可能会给出 A->B,B->A的数据,需要我们记录是否节点已经录入完成,已完成的不能再次处理,防止死循环。
后来才知道,出题人没那么坏,他给出的数据确实是先是爹,后是儿子,没有例外,良心!

4、最大深度:这个好办,创建树时就已经逐个+1进行创建,可以遍历一次找出最大的深度即可。

5、最大宽度:这个也好办,因为树是现成的,我们只需要一个桶来记录每层的个数,找出最大个数就行了。

6、结点u到结点v间距离
这个是本题的关键所在!因为题目中说的很含糊,什么到根的边数,根本不是到根,是到它们两个结点的公共最近祖先

获取两个结点的公共最近祖先有现成的算法:

//最近公共祖先默认是根结点,就是1,英文简写:lowestCommonAncestor
int lca(int x, int y) {
    st[x] = 1;                      //把x的节点记录已走过
    while (t[x].father) {           //遍历至根节点
        x = t[x].father;            //更新遍历爸爸
        st[x] = 1;                  //记录已走过
    }
    //遍历至x节点已走过的节点,找到最近公共祖先
    while (!st[y])y = t[y].father;
    return y;
}
//按题意输出
    cout << (t[u].depth - t[r].depth) * 2 + (t[v].depth - t[r].depth) << endl;

二、C++代码

#include <bits/stdc++.h>

using namespace std;
int n, x, y, u, v;
const int N = 110;

//树的结构体+存储数组
struct Node {
    int id;     // 当前结点ID
    int father; // 爸爸
    int left;   // 左结点ID
    int right;  // 右结点ID
    int depth;  // 深度,不用再次dfs去探测
} t[N];

//记录边的关系
typedef pair<int, int> PII;
PII a[N];

//是不是使用过
int st[N];

//用来计某一层的结点个数
int bucket[N];

//通过递归构建二叉树
void dfs(int u) {
    //u是老大,找出它的左儿子和右儿子
    for (int i = 1; i < n; i++) {//遍历所有关系
        if (a[i].first == u) {
            //左结点为空,放入左结点
            if (t[u].left == 0) t[u].left = a[i].second;
                //否则放入右结点
            else t[u].right = a[i].second;
            //修改深度标识
            t[a[i].second].depth = t[u].depth + 1;
            t[a[i].second].father = u;
            //递归创建子树
            dfs(a[i].second);
        }
    }
}

//最近公共祖先默认是根结点,就是1,英文简写:lowestCommonAncestor
int lca(int x, int y) {
    st[x] = 1;                      //把x的节点记录已走过
    while (t[x].father) {           //遍历至根节点
        x = t[x].father;            //更新遍历爸爸
        st[x] = 1;                  //记录已走过
    }
    //遍历至x节点已走过的节点,找到最近公共祖先
    while (!st[y])y = t[y].father;
    return y;
}

int main() {
    //二叉树结点个数
    cin >> n;
    //接下来的n-1行
    for (int i = 1; i < n; i++) {
        //构建二叉树
        cin >> x >> y;
        //记录关系
        a[i] = {x, y};
    }
    //通过递归建树,构建二叉树
    t[1].depth = 1, t[1].id = 1, t[1].father = 0, st[1] = 1;//根结点1
    dfs(1);

    //表示求从结点u到结点v的距离
    cin >> u >> v;

    //二叉树的深度
    int maxDepth = 0;
    for (int i = 1; i <= n; i++) maxDepth = max(maxDepth, t[i].depth);
    cout << maxDepth << endl;

    //二叉树的宽度
    int maxWidth = 0;
    for (int i = 1; i <= n; i++) bucket[t[i].depth]++;
    for (int i = 1; i <= n; i++) maxWidth = max(maxWidth, bucket[i]);
    cout << maxWidth << endl;

    //结点u到结点v间距离:结点间距离的定义:由结点向根方向(上行方向)时的边数×2,与由根向叶结点方向(下行方向)时的边数之和。
    //这里有一个最近公共祖先的问题,才能拿到正确答案
    int r = lca(u, v);
    //按题意输出
    cout << (t[u].depth - t[r].depth) * 2 + (t[v].depth - t[r].depth) << endl;
    return 0;
}

三、防止出题人使坏的代码

#include <bits/stdc++.h>

using namespace std;
int n, x, y, u, v;
const int N = 110;

//树的结构体+存储数组
struct Node {
    int id;     // 当前结点ID
    int father; // 爸爸
    int left;   // 左结点ID
    int right;  // 右结点ID
    int depth;  // 深度,不用再次dfs去探测
} t[N];

//记录边的关系
typedef pair<int, int> PII;
PII a[N];

//是不是使用过
int st[N];

//用来计某一层的结点个数
int bucket[N];

//通过递归构建二叉树
void dfs(int u) {
    //u是老大,找出它的左儿子和右儿子
    for (int i = 1; i < n; i++) {//遍历所有关系
        if (a[i].first == u && !st[a[i].second]) {
            //标识已使用
            st[a[i].second] = 1;
            //左结点为空,放入左结点
            if (t[u].left == 0) t[u].left = a[i].second;
            //否则放入右结点
            else t[u].right = a[i].second;
            //修改深度标识
            t[a[i].second].depth = t[u].depth + 1;
            t[a[i].second].father = u;
            //递归创建子树
            dfs(a[i].second);
        } else if (a[i].second == u && !st[a[i].first]) {
            st[a[i].first] = 1;
            if (t[u].left == 0) t[u].left = a[i].first;
            else t[u].right = a[i].first;
            t[a[i].first].depth = t[u].depth + 1;
            t[a[i].first].father = u;
            dfs(a[i].first);
        }
    }
}

//最近公共祖先默认是根结点,就是1,英文简写:lowestCommonAncestor
int lca(int x, int y) {
    st[x] = 1;                      //把x的节点记录已走过
    while (t[x].father) {           //遍历至根节点
        x = t[x].father;            //更新遍历爸爸
        st[x] = 1;                  //记录已走过
    }
    //遍历至x节点已走过的节点,找到最近公共祖先
    while (!st[y])y = t[y].father;
    return y;
}

int main() {
    //二叉树结点个数
    cin >> n;
    //接下来的n-1行
    for (int i = 1; i < n; i++) {
        //构建二叉树
        cin >> x >> y;
        //记录关系
        a[i] = {x, y};
    }
    //通过递归建树,构建二叉树
    t[1].depth = 1, t[1].id = 1, t[1].father = 0, st[1] = 1;//根结点1
    dfs(1);

    //表示求从结点u到结点v的距离
    cin >> u >> v;

    //二叉树的深度
    int maxDepth = 0;
    for (int i = 1; i <= n; i++) maxDepth = max(maxDepth, t[i].depth);
    cout << maxDepth << endl;

    //二叉树的宽度
    int maxWidth = 0;
    for (int i = 1; i <= n; i++) bucket[t[i].depth]++;
    for (int i = 1; i <= n; i++) maxWidth = max(maxWidth, bucket[i]);
    cout << maxWidth << endl;

    //结点u到结点v间距离:结点间距离的定义:由结点向根方向(上行方向)时的边数×2,与由根向叶结点方向(下行方向)时的边数之和。
    //这里有一个最近公共祖先的问题,才能拿到正确答案
    memset(st, 0, sizeof st);
    int r = lca(u, v);
    //按题意输出
    cout << (t[u].depth - t[r].depth) * 2 + (t[v].depth - t[r].depth) << endl;
    return 0;
}
原文地址:https://www.cnblogs.com/littlehb/p/15103532.html