Number of Ways to Assign Edge Weights II

hard tree LCA combinatorics modular math

Problem

An undirected tree has n nodes labelled 1..n, rooted at node 1, given by edges. Every edge must be assigned a weight of 1 or 2. The cost of a path is the sum of its edge weights. For each query [u, v], count the number of weight assignments to the edges on the path from u to v that make the path cost odd, modulo 109+7.

A weight 2 is even and a weight 1 is odd, so only the weight-1 edges flip the parity of the cost. The cost is odd exactly when an odd number of the m path edges get weight 1. Choosing an odd-sized subset of m items can be done in 2m−1 ways (and 0 ways when m = 0). So each answer is 2dist(u,v)−1, where dist is the number of edges between u and v.

Inputedges = [[1,2],[1,3],[3,4],[3,5]], queries = [[1,4],[3,4],[2,5]]
Output[2, 1, 4]
dist(1,4)=2 → 2¹=2; dist(3,4)=1 → 2⁰=1; dist(2,5)=3 → 2²=4.

def assignEdgeWeights(edges, queries):
    MOD = 10**9 + 7
    n = len(edges) + 1
    g = [[] for _ in range(n + 1)]
    for u, v in edges:                       # build adjacency list
        g[u].append(v)
        g[v].append(u)
    LOG = max(1, n.bit_length())
    depth = [0] * (n + 1)
    up = [[0] * (n + 1) for _ in range(LOG)] # up[k][v] = 2^k-th ancestor
    seen = [False] * (n + 1)
    seen[1] = True
    stack = [1]                              # iterative DFS from root 1
    while stack:
        node = stack.pop()
        for nb in g[node]:
            if not seen[nb]:
                seen[nb] = True
                depth[nb] = depth[node] + 1
                up[0][nb] = node             # immediate parent
                stack.append(nb)
    for k in range(1, LOG):                  # binary-lifting table
        for v in range(1, n + 1):
            up[k][v] = up[k - 1][up[k - 1][v]]

    def lca(u, v):
        if depth[u] < depth[v]:
            u, v = v, u
        diff = depth[u] - depth[v]           # lift u up to v's depth
        for k in range(LOG):
            if (diff >> k) & 1:
                u = up[k][u]
        if u == v:
            return u
        for k in range(LOG - 1, -1, -1):     # rise together until parents meet
            if up[k][u] != up[k][v]:
                u, v = up[k][u], up[k][v]
        return up[0][u]

    ans = []
    for u, v in queries:
        w = lca(u, v)
        dist = depth[u] + depth[v] - 2 * depth[w]
        ans.append(pow(2, dist - 1, MOD) if dist >= 1 else 0)
    return ans
function assignEdgeWeights(edges, queries) {
  const MOD = 1000000007n;
  const n = edges.length + 1;
  const g = Array.from({ length: n + 1 }, () => []);
  for (const [u, v] of edges) {            // build adjacency list
    g[u].push(v);
    g[v].push(u);
  }
  const LOG = Math.max(1, 32 - Math.clz32(n));
  const depth = new Array(n + 1).fill(0);
  const up = Array.from({ length: LOG }, () => new Array(n + 1).fill(0));
  const seen = new Array(n + 1).fill(false);
  seen[1] = true;
  const stack = [1];                        // iterative DFS from root 1
  while (stack.length) {
    const node = stack.pop();
    for (const nb of g[node]) {
      if (!seen[nb]) {
        seen[nb] = true;
        depth[nb] = depth[node] + 1;
        up[0][nb] = node;                    // immediate parent
        stack.push(nb);
      }
    }
  }
  for (let k = 1; k < LOG; k++)              // binary-lifting table
    for (let v = 1; v <= n; v++)
      up[k][v] = up[k - 1][up[k - 1][v]];

  const lca = (u, v) => {
    if (depth[u] < depth[v]) [u, v] = [v, u];
    let diff = depth[u] - depth[v];          // lift u up to v's depth
    for (let k = 0; k < LOG; k++)
      if ((diff >> k) & 1) u = up[k][u];
    if (u === v) return u;
    for (let k = LOG - 1; k >= 0; k--)        // rise together
      if (up[k][u] !== up[k][v]) { u = up[k][u]; v = up[k][v]; }
    return up[0][u];
  };

  const power = (e) => {                      // 2^e mod p
    let r = 1n, b = 2n;
    while (e > 0) { if (e & 1) r = r * b % MOD; b = b * b % MOD; e >>= 1; }
    return r;
  };
  const ans = [];
  for (const [u, v] of queries) {
    const w = lca(u, v);
    const dist = depth[u] + depth[v] - 2 * depth[w];
    ans.push(dist >= 1 ? Number(power(dist - 1)) : 0);
  }
  return ans;
}
int[] assignEdgeWeights(int[][] edges, int[][] queries) {
    final long MOD = 1_000_000_007L;
    int n = edges.length + 1;
    List<List<Integer>> g = new ArrayList<>();
    for (int i = 0; i <= n; i++) g.add(new ArrayList<>());
    for (int[] e : edges) {                  // build adjacency list
        g.get(e[0]).add(e[1]);
        g.get(e[1]).add(e[0]);
    }
    int LOG = Math.max(1, 32 - Integer.numberOfLeadingZeros(n));
    int[] depth = new int[n + 1];
    int[][] up = new int[LOG][n + 1];        // up[k][v] = 2^k-th ancestor
    boolean[] seen = new boolean[n + 1];
    seen[1] = true;
    Deque<Integer> stack = new ArrayDeque<>();
    stack.push(1);                           // iterative DFS from root 1
    while (!stack.isEmpty()) {
        int node = stack.pop();
        for (int nb : g.get(node)) if (!seen[nb]) {
            seen[nb] = true;
            depth[nb] = depth[node] + 1;
            up[0][nb] = node;                // immediate parent
            stack.push(nb);
        }
    }
    for (int k = 1; k < LOG; k++)            // binary-lifting table
        for (int v = 1; v <= n; v++)
            up[k][v] = up[k - 1][up[k - 1][v]];

    int[] ans = new int[queries.length];
    for (int i = 0; i < queries.length; i++) {
        int u = queries[i][0], v = queries[i][1];
        int w = lca(u, v, depth, up, LOG);
        int dist = depth[u] + depth[v] - 2 * depth[w];
        ans[i] = dist >= 1 ? (int) power(2, dist - 1, MOD) : 0;
    }
    return ans;
}

int lca(int u, int v, int[] depth, int[][] up, int LOG) {
    if (depth[u] < depth[v]) { int t = u; u = v; v = t; }
    int diff = depth[u] - depth[v];          // lift u up to v's depth
    for (int k = 0; k < LOG; k++)
        if (((diff >> k) & 1) == 1) u = up[k][u];
    if (u == v) return u;
    for (int k = LOG - 1; k >= 0; k--)        // rise together
        if (up[k][u] != up[k][v]) { u = up[k][u]; v = up[k][v]; }
    return up[0][u];
}

long power(long b, long e, long MOD) {       // b^e mod p
    long r = 1;
    while (e > 0) { if ((e & 1) == 1) r = r * b % MOD; b = b * b % MOD; e >>= 1; }
    return r;
}
vector<int> assignEdgeWeights(vector<vector<int>>& edges,
                              vector<vector<int>>& queries) {
    const long long MOD = 1000000007LL;
    int n = edges.size() + 1;
    vector<vector<int>> g(n + 1);
    for (auto& e : edges) {                  // build adjacency list
        g[e[0]].push_back(e[1]);
        g[e[1]].push_back(e[0]);
    }
    int LOG = max(1, 32 - __builtin_clz(n));
    vector<int> depth(n + 1, 0);
    vector<vector<int>> up(LOG, vector<int>(n + 1, 0)); // 2^k-th ancestor
    vector<bool> seen(n + 1, false);
    seen[1] = true;
    vector<int> stk = {1};                    // iterative DFS from root 1
    while (!stk.empty()) {
        int node = stk.back(); stk.pop_back();
        for (int nb : g[node]) if (!seen[nb]) {
            seen[nb] = true;
            depth[nb] = depth[node] + 1;
            up[0][nb] = node;                // immediate parent
            stk.push_back(nb);
        }
    }
    for (int k = 1; k < LOG; k++)            // binary-lifting table
        for (int v = 1; v <= n; v++)
            up[k][v] = up[k - 1][up[k - 1][v]];

    auto lca = [&](int u, int v) {
        if (depth[u] < depth[v]) swap(u, v);
        int diff = depth[u] - depth[v];      // lift u up to v's depth
        for (int k = 0; k < LOG; k++)
            if ((diff >> k) & 1) u = up[k][u];
        if (u == v) return u;
        for (int k = LOG - 1; k >= 0; k--)    // rise together
            if (up[k][u] != up[k][v]) { u = up[k][u]; v = up[k][v]; }
        return up[0][u];
    };
    auto power = [&](long long e) {           // 2^e mod p
        long long r = 1, b = 2;
        while (e > 0) { if (e & 1) r = r * b % MOD; b = b * b % MOD; e >>= 1; }
        return r;
    };
    vector<int> ans;
    for (auto& q : queries) {
        int w = lca(q[0], q[1]);
        int dist = depth[q[0]] + depth[q[1]] - 2 * depth[w];
        ans.push_back(dist >= 1 ? (int) power(dist - 1) : 0);
    }
    return ans;
}
Time: O((n + q) log n) Space: O(n log n)