核心思路

本题给定的是无向带权树,树中任意两点间有且仅有一条简单路径。对于每个查询 (src1, src2, dest),包含两条路径的最小连通子图就是这两条路径的并集。

关键公式:三条路径 (src1→src2)、(src1→dest)、(src2→dest) 的并集总权重为:

```
(dist(src1, src2) + dist(src1, dest) + dist(src2, dest)) / 2
```

因为并集中的每条边在三段两两距离中恰好被计算两次。

预处理使用二进制提升(Binary Lifting) 求LCA,单次查询 O(log n)。

完整 JavaScript 实现

```javascript
/**
 * @param {number[][]} edges - 边列表 [u, v, w]
 * @param {number[][]} queries - 查询列表 [src1, src2, dest]
 * @return {number[]} - 每个查询的最小总权重
 */
function minimumWeight(edges, queries) {
    const n = edges.length + 1;
    const graph = Array.from({ length: n }, () => []);
    
    // 构建无向图
    for (const [u, v, w] of edges) {
        graph[u].push([v, w]);
        graph[v].push([u, w]);
    }
    
    // DFS 预处理 parent 和 depth
    const parent = Array(n).fill([-1, 0]);
    const depth = Array(n).fill(0);
    
    function dfs(node, par, parWeight, d) {
        depth[node] = d;
        parent[node] = [par, parWeight];
        for (const [nei, weight] of graph[node]) {
            if (nei === par) continue;
            dfs(nei, node, weight, d + 1);
        }
    }
    dfs(0, -1, 0, 0);
    
    const lca = new LCA(n, parent, depth);
    const ans = [];
    
    for (const [src1, src2, dest] of queries) {
        const d1 = lca.getDist(src1, src2);
        const d2 = lca.getDist(src1, dest);
        const d3 = lca.getDist(src2, dest);
        ans.push((d1 + d2 + d3) / 2);
    }
    
    return ans;
}

class LCA {
    constructor(n, parent, depth) {
        this.maxDepth = Math.ceil(Math.log2(n)) + 1;
        this.depth = depth;
        
        // up[k][v] = v 的 2^k 级祖先
        this.up = Array.from({ length: this.maxDepth }, () => Array(n).fill(-1));
        // sum[k][v] = v 到其 2^k 级祖先的路径权重和
        this.sum = Array.from({ length: this.maxDepth }, () => Array(n).fill(0));
        
        // 初始化第 0 层
        for (let v = 0; v < n; v++) {
            this.up[0][v] = parent[v][0];
            this.sum[0][v] = parent[v][1];
        }
        
        // 递推计算更高层
        for (let k = 1; k < this.maxDepth; k++) {
            for (let v = 0; v < n; v++) {
                const mid = this.up[k - 1][v];
                if (mid !== -1) {
                    this.up[k][v] = this.up[k - 1][mid];
                    this.sum[k][v] = this.sum[k - 1][v] + this.sum[k - 1][mid];
                }
            }
        }
    }
    
    // 获取 LCA
    getLCA(a, b) {
        if (this.depth[a] > this.depth[b]) [a, b] = [b, a];
        
        // 将 b 提升到与 a 同深度
        let diff = this.depth[b] - this.depth[a];
        for (let k = 0; k < this.maxDepth; k++) {
            if ((diff >> k) & 1) {
                b = this.up[k][b];
            }
        }
        
        if (a === b) return a;
        
        // 同时提升
        for (let k = this.maxDepth - 1; k >= 0; k--) {
            if (this.up[k][a] !== this.up[k][b]) {
                a = this.up[k][a];
                b = this.up[k][b];
            }
        }
        
        return this.up[0][a];
    }
    
    // 获取 a 到 b 的路径权重和
    getDist(a, b) {
        const lca = this.getLCA(a, b);
        return this._getUpSum(a, this.depth[a] - this.depth[lca]) +
               this._getUpSum(b, this.depth[b] - this.depth[lca]);
    }
    
    // 获取节点 v 到其 k 级祖先的路径权重和(内部方法)
    _getUpSum(v, k) {
        let sum = 0;
        for (let i = 0; i < this.maxDepth; i++) {
            if ((k >> i) & 1) {
                sum += this.sum[i][v];
                v = this.up[i][v];
            }
        }
        return sum;
    }
}

// 测试用例
console.log(minimumWeight(
    [[0,1,2],[1,2,3],[1,3,5],[1,4,4],[2,5,6]],
    [[2,3,4],[0,2,5]]
)); // [12, 11]

console.log(minimumWeight(
    [[1,0,8],[0,2,7]],
    [[0,1,2]]
)); // [15]
```

复杂度分析

项目 复杂度
预处理(DFS + 二进制表) O(n log n)
单次查询 O(log n)
空间 O(n log n)

其中 n 为节点数,m 为查询数。

 

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐