1 条题解

  • 0
    @ 2025-11-11 10:51:57

    问题分析

    我们有一个深度为 dd 的满二叉树,节点编号规则为:

    • 根节点为 11
    • 节点 xx 的左孩子是 2x2x,右孩子是 2x+12x+1

    树中节点编号从 112d12^d - 1

    题目有两个问题:

    1. 问题1 (c=1c=1):求节点 aa 到节点 bb 的最小路径编号和
    2. 问题2 (c=2c=2):求树中其他简单路径的编号和等于上述最小值的路径条数

    关键观察

    1. 最小路径和的计算

    在二叉树中,两个节点之间的最短路径必然经过它们的最近公共祖先 (LCA)

    路径和 = 从 aa 到 LCA 的路径和 + 从 bb 到 LCA 的路径和 - LCA 的编号(因为被重复计算了一次)

    2. LCA 的求法

    在这种编号的满二叉树中,可以通过不断将较大编号除以 2 来找到 LCA:

    def lca(a, b):
        while a != b:
            if a > b:
                a //= 2
            else:
                b //= 2
        return a
    

    3. 路径和的计算

    从节点 xx 到根节点的路径和可以通过不断除以 2 来累加:

    def path_sum(x):
        total = 0
        while x > 0:
            total += x
            x //= 2
        return total
    

    那么 aabb 的最小路径和为: path_sum(a) + path_sum(b) - 2 * path_sum(lca) + lca

    4. 统计相同路径和的其他路径

    对于问题2,我们需要找到所有简单路径(起点和终点可以相同)的编号和等于目标值的路径条数,但要排除 aabb 的这条路径。

    重要性质:在这种满二叉树中,任何简单路径都可以看作是从某个节点 uu 到某个节点 vv 的路径。

    我们可以枚举所有可能的路径起点 uu 和终点 vv,计算路径和,统计等于目标值的路径数量,最后减去 aabb 这条路径(如果它的和等于目标值)。

    算法实现

    步骤1:计算 LCA

    def get_lca(a, b):
        while a != b:
            if a > b:
                a //= 2
            else:
                b //= 2
        return a
    

    步骤2:计算从节点到根的路径和

    def get_path_to_root(x):
        total = 0
        while x > 0:
            total += x
            x //= 2
        return total
    

    步骤3:计算两个节点间的路径和

    def get_path_sum(a, b):
        lca_node = get_lca(a, b)
        return get_path_to_root(a) + get_path_to_root(b) - 2 * get_path_to_root(lca_node) + lca_node
    

    步骤4:统计路径条数

    def count_paths(d, target, exclude_a, exclude_b):
        count = 0
        max_node = (1 << d) - 1  # 最大节点编号
        
        # 枚举所有节点对 (u, v)
        for u in range(1, max_node + 1):
            for v in range(u, max_node + 1):  # 从u开始避免重复计数
                if get_path_sum(u, v) == target:
                    # 检查是否是要排除的路径
                    if sorted([u, v]) != sorted([exclude_a, exclude_b]):
                        count += 1
        return count
    

    优化考虑

    对于 d50d \leq 50 的情况,节点数量最多为 25012^{50} - 1,直接枚举所有节点对显然不可行。我们需要更高效的方法。

    优化思路

    1. 路径和可以分解为:路径和 = (u到根的路径和 + v到根的路径和 - 2 * LCA到根的路径和 + LCA)
    2. 我们可以预处理所有节点到根的路径和
    3. 利用树的结构性质来减少枚举

    但由于时间限制,这里给出一个适用于 d30d \leq 30 的实现(2301092^{30} \approx 10^9 仍然太大,实际中需要进一步优化)。

    代码实现(适用于小数据)

    #include <iostream>
    #include <algorithm>
    #include <vector>
    using namespace std;
    
    typedef long long ll;
    
    // 求最近公共祖先
    ll lca(ll a, ll b) {
        while (a != b) {
            if (a > b) a /= 2;
            else b /= 2;
        }
        return a;
    }
    
    // 求节点到根的路径和
    ll path_to_root(ll x) {
        ll sum = 0;
        while (x > 0) {
            sum += x;
            x /= 2;
        }
        return sum;
    }
    
    // 求两个节点间的路径和
    ll path_sum(ll a, ll b) {
        ll l = lca(a, b);
        return path_to_root(a) + path_to_root(b) - 2 * path_to_root(l) + l;
    }
    
    int main() {
        int T;
        cin >> T;
        
        while (T--) {
            int d, c;
            ll a, b;
            cin >> d >> a >> b >> c;
            
            ll target = path_sum(a, b);
            
            if (c == 1) {
                // 问题1:直接输出路径和
                cout << target << endl;
            } else {
                // 问题2:统计其他路径条数
                ll max_node = (1LL << d) - 1;
                ll count = 0;
                
                // 枚举所有节点对
                for (ll u = 1; u <= max_node; u++) {
                    for (ll v = u; v <= max_node; v++) {
                        if (path_sum(u, v) == target) {
                            // 排除a到b的路径
                            if (!(min(u, v) == min(a, b) && max(u, v) == max(a, b))) {
                                count++;
                            }
                        }
                    }
                }
                
                cout << count << endl;
            }
        }
        
        return 0;
    }
    

    进一步优化思路

    对于大数据范围 (d50d \leq 50),我们需要更聪明的算法:

    1. 利用路径和的性质:任何路径都可以唯一地由它的端点和LCA确定
    2. 动态规划:用DP统计特定路径和的数量
    3. 数学方法:分析路径和的数学性质,找到计算公式

    样例验证

    用题目样例验证:

    • 3 1 1 1: 路径 1-1,和为 1 ✓
    • 3 1 1 2: 没有其他路径和为 1 ✓
    • 3 1 2 1: 路径 1-2,和为 1+2=3 ✓
    • 3 1 2 2: 路径 3-3 和为 3 ✓
    • 等等...

    总结

    这道题的关键在于:

    1. 理解满二叉树中路径和的计算方法
    2. 掌握LCA在特殊编号二叉树中的求法
    3. 对于问题2,需要高效统计满足条件的路径数量
    • 1

    信息

    ID
    5223
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者