• 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;
    }
    
  • 相关阅读:
    openVolumeMesh example 程序学习
    使用字符串创建java 对象
    HDU-1501-Zipper
    UVA-10285-Longest Run on a Snowboard
    HDU-2182-Frog
    HDU-2044-一只小蜜蜂
    POJ-1163-The Triangle
    HDU-1159-Common Subsequence
    HDU-2069-Coin Change
    HDU-4864-Task
  • 原文地址:https://www.cnblogs.com/littlehb/p/15103532.html
Copyright © 2020-2023  润新知