Hard

题目描述

有一个包含n个节点的无向树,节点标号从1到n,根节点为1。树用长度为n-1的二维整数数组edges表示,其中edges[i] = [ui, vi]表示节点ui和vi之间有一条边。

初始时,所有边的权重都为0。你必须给每条边分配权重1或2。

任意两个节点u和v之间路径的代价是连接它们路径上所有边的权重总和。

给定一个二维整数数组queries。对于每个queries[i] = [ui, vi],确定有多少种给边分配权重的方法,使得节点ui和vi之间路径的代价为奇数。

返回一个数组answer,其中answer[i]是queries[i]的有效分配方案数。

由于答案可能很大,对每个answer[i]取模109 + 7。

注意:对于每个查询,忽略不在节点ui和vi之间路径上的所有边。

示例1:

输入: edges = [[1,2]], queries = [[1,1],[1,2]]
输出: [0,1]
解释:
- 查询[1,1]: 从节点1到自己的路径不包含任何边,代价为0。因此有效分配数为0。
- 查询[1,2]: 从节点1到节点2的路径包含一条边(1→2)。分配权重1使代价为奇数,分配权重2使代价为偶数。因此有效分配数为1。

示例2:

输入: edges = [[1,2],[1,3],[3,4],[3,5]], queries = [[1,4],[3,4],[2,5]]
输出: [2,1,4]

约束条件:

  • 2 <= n <= 10^5
  • edges.length == n - 1
  • edges[i] == [ui, vi]
  • 1 <= queries.length <= 10^5
  • queries[i] == [ui, vi]
  • 1 <= ui, vi <= n
  • edges表示一个有效的树

解题思路

这是一个树上路径问题,核心思路是动态规划 + 最近公共祖先(LCA)。

思路分析:

  1. 路径代价的奇偶性:要使路径代价为奇数,路径上权重为1的边数必须为奇数。每条边可以分配权重1或2,这本质上是一个组合问题。

  2. 动态规划状态:设dp[len][parity]表示长度为len的路径中,有parity个奇数权重边(权重为1)的方案数。状态转移为:

    • dp[i][0] = dp[i-1][1] + dp[i-1][0](当前边选权重2或1)
    • dp[i][1] = dp[i-1][0] + dp[i-1][1](当前边选权重1或2)
  3. 路径长度计算:使用LCA算法快速计算任意两点间的距离。对于查询[u,v],路径长度为depth[u] + depth[v] - 2*depth[lca(u,v)]。

  4. 优化观察:通过数学分析可以发现,对于长度为k的路径,使代价为奇数的方案数为2^(k-1)(当k>0时),k=0时方案数为0。

算法步骤:

  1. 构建树的邻接表表示
  2. 使用DFS预处理每个节点的深度和父节点信息,构建LCA查询结构
  3. 对每个查询,计算路径长度并应用公式2^(k-1)

这种方法避免了复杂的DP计算,直接利用数学性质求解,时间复杂度更优。

代码实现

class Solution {
public:
    const int MOD = 1e9 + 7;
    
    vector<int> assignEdgeWeights(vector<vector<int>>& edges, vector<vector<int>>& queries) {
        int n = edges.size() + 1;
        vector<vector<int>> adj(n + 1);
        
        for (auto& edge : edges) {
            adj[edge[0]].push_back(edge[1]);
            adj[edge[1]].push_back(edge[0]);
        }
        
        // LCA preprocessing
        int LOG = 20;
        vector<vector<int>> up(n + 1, vector<int>(LOG));
        vector<int> depth(n + 1);
        
        function<void(int, int)> dfs = [&](int u, int p) {
            up[u][0] = p;
            for (int i = 1; i < LOG; i++) {
                up[u][i] = up[up[u][i-1]][i-1];
            }
            for (int v : adj[u]) {
                if (v != p) {
                    depth[v] = depth[u] + 1;
                    dfs(v, u);
                }
            }
        };
        
        depth[1] = 0;
        dfs(1, 1);
        
        auto lca = [&](int u, int v) {
            if (depth[u] < depth[v]) swap(u, v);
            
            int diff = depth[u] - depth[v];
            for (int i = 0; i < LOG; i++) {
                if ((diff >> i) & 1) {
                    u = up[u][i];
                }
            }
            
            if (u == v) return u;
            
            for (int i = LOG - 1; i >= 0; i--) {
                if (up[u][i] != up[v][i]) {
                    u = up[u][i];
                    v = up[v][i];
                }
            }
            
            return up[u][0];
        };
        
        auto power = [&](long long base, int exp) {
            long long result = 1;
            while (exp > 0) {
                if (exp & 1) result = (result * base) % MOD;
                base = (base * base) % MOD;
                exp >>= 1;
            }
            return (int)result;
        };
        
        vector<int> result;
        for (auto& query : queries) {
            int u = query[0], v = query[1];
            int pathLength = depth[u] + depth[v] - 2 * depth[lca(u, v)];
            
            if (pathLength == 0) {
                result.push_back(0);
            } else {
                result.push_back(power(2, pathLength - 1));
            }
        }
        
        return result;
    }
};
class Solution:
    def assignEdgeWeights(self, edges: List[List[int]], queries: List[List[int]]) -> List[int]:
        MOD = 10**9 + 7
        n = len(edges) + 1
        adj = [[] for _ in range(n + 1)]
        
        for u, v in edges:
            adj[u].append(v)
            adj[v].append(u)
        
        # LCA preprocessing
        LOG = 20
        up = [[0] * LOG for _ in range(n + 1)]
        depth = [0] * (n + 1)
        
        def dfs(u, p):
            up[u][0] = p
            for i in range(1, LOG):
                up[u][i] = up[up[u][i-1]][i-1]
            
            for v in adj[u]:
                if v != p:
                    depth[v] = depth[u] + 1
                    dfs(v, u)
        
        dfs(1, 1)
        
        def lca(u, v):
            if depth[u] < depth[v]:
                u, v = v, u
            
            diff = depth[u] - depth[v]
            for i in range(LOG):
                if (diff >> i) & 1:
                    u = up[u][i]
            
            if u == v:
                return u
            
            for i in range(LOG - 1, -1, -1):
                if up[u][i] != up[v][i]:
                    u = up[u][i]
                    v = up[v][i]
            
            return up[u][0]
        
        result = []
        for u, v in queries:
            path_length = depth[u] + depth[v] - 2 * depth[lca(u, v)]
            
            if path_length == 0:
                result.append(0)
            else:
                result.append(pow(2, path_length - 1, MOD))
        
        return result
public class Solution {
    private const int MOD = 1000000007;
    
    public int[] AssignEdgeWeights(int[][] edges, int[][] queries) {
        int n = edges.Length + 1;
        var adj = new List<int>[n + 1];
        for (int i = 0; i <= n; i++) {
            adj[i] = new List<int>();
        }
        
        foreach (var edge in edges) {
            adj[edge[0]].Add(edge[1]);
            adj[edge[1]].Add(edge[0]);
        }
        
        // LCA preprocessing
        int LOG = 20;
        var up = new int[n + 1][];
        for (int i = 0; i <= n; i++) {
            up[i] = new int[LOG];
        }
        var depth = new int[n + 1];
        
        void Dfs(int u, int p) {
            up[u][0] = p;
            for (int i = 1; i < LOG; i++) {
                up[u][i] = up[up[u][i-1]][i-1];
            }
            
            foreach (int v in adj[u]) {
                if (v != p) {
                    depth[v] = depth[u] + 1;
                    Dfs(v, u);
                }
            }
        }
        
        Dfs(1, 1);
        
        int Lca(int u, int v) {
            if (depth[u] < depth[v]) {
                (u, v) = (v, u);
            }
            
            int diff = depth[u] - depth[v];
            for (int i = 0; i < LOG; i++) {
                if ((diff >> i & 1) == 1) {
                    u = up[u][i];
                }
            }
            
            if (u == v) return u;
            
            for (int i = LOG - 1; i >= 0; i--) {
                if (up[u][i] != up[v][i]) {
                    u = up[u][i];
                    v = up[v][i];
                }
            }
            
            return up[u][0];
        }
        
        long Power(long baseNum, int exp) {
            long result = 1;
            while (exp > 0) {
                if ((exp & 1) == 1) {
                    result = (result * baseNum) % MOD;
                }
                baseNum = (baseNum * baseNum) % MOD;
                exp >>= 1;
            }
            return result;
        }
        
        var result = new int[queries.Length];
        for (int i = 0; i < queries.Length; i++) {
            int u = queries[i][0], v = queries[i][1];
            int pathLength = depth[u] + depth[v] - 2 * depth[Lca(u, v)];
            
            if (pathLength == 0) {
                result[i] = 0;
            } else {
                result[i] = (int)Power(2, pathLength - 1);
            }
        }
        
        return result;
    }
}
var assignEdgeWeights = function(edges, queries) {
    const MOD = 1000000007;
    const n = edges.length + 1;
    
    // Build adjacency list
    const adj = Array(n + 1).fill().map(() => []);
    for (const [u, v] of edges) {
        adj[u].push(v);
        adj[v].push(u);
    }
    
    // Build parent relationships using DFS from root 1
    const parent = Array(n + 1).fill(-1);
    const visited = Array(n + 1).fill(false);
    
    function dfs(node) {
        visited[node] = true;
        for (const neighbor of adj[node]) {
            if (!visited[neighbor]) {
                parent[neighbor] = node;
                dfs(neighbor);
            }
        }
    }
    
    dfs(1);
    
    // Find path between two nodes
    function findPath(u, v) {
        const pathU = [];
        const pathV = [];
        
        let curr = u;
        while (curr !== -1) {
            pathU.push(curr);
            curr = parent[curr];
        }
        
        curr = v;
        while (curr !== -1) {
            pathV.push(curr);
            curr = parent[curr];
        }
        
        // Find LCA
        const setU = new Set(pathU);
        let lca = -1;
        for (const node of pathV) {
            if (setU.has(node)) {
                lca = node;
                break;
            }
        }
        
        // Build path from u to v through lca
        const path = [];
        curr = u;
        while (curr !== lca) {
            path.push(curr);
            curr = parent[curr];
        }
        path.push(lca);
        
        const pathToV = [];
        curr = v;
        while (curr !== lca) {
            pathToV.push(curr);
            curr = parent[curr];
        }
        
        for (let i = pathToV.length - 1; i >= 0; i--) {
            path.push(pathToV[i]);
        }
        
        return path;
    }
    
    const result = [];
    
    for (const [u, v] of queries) {
        if (u === v) {
            result.push(0);
            continue;
        }
        
        const path = findPath(u, v);
        const numEdges = path.length - 1;
        
        if (numEdges === 0) {
            result.push(0);
            continue;
        }
        
        // For k edges, we need odd sum
        // Each edge can be 1 or 2
        // We need odd number of odd weights (weight 1)
        // Number of ways = sum of C(k, i) for odd i from 1 to k
        // This equals 2^(k-1) when k > 0
        
        let ways = 1;
        for (let i = 0; i < numEdges - 1; i++) {
            ways = (ways * 2) % MOD;
        }
        
        result.push(ways);
    }
    
    return result;
};

复杂度分析

项目复杂度
时间复杂度O(n log n + q log n)
空间复杂度O(n log n)

其中 n 是节点数,q 是查询数。时间复杂度包括 O(n log n) 的LCA预处理和 O(q log n) 的查询处理。空间复杂度主要由LCA的二进制提升表占用。