Skip to content

tree_path_affine_sum.hpp

SECTIONGraph INCLUDEnoya/tree_path_affine_sum.hpp

Sum edge-affine path transforms from every possible root. For an oriented edge x-y, the message sent by y is the sum of path values whose destinations lie on y's side after deleting the edge. Applying bx+c to all of them gives b times that message plus c times the side size. One postorder pass builds child-side messages; one preorder reroot pass supplies the complementary parent-side message.

Verified by tree_path_composite_sum.

维护树上点的仿射函数,并查询一条有向路径上的函数复合结果。

Implementation

View on GitHub

#ifndef NOYA_TREE_PATH_AFFINE_SUM_HPP
#define NOYA_TREE_PATH_AFFINE_SUM_HPP 1

/// @complexity Time: O(n).
/// Space: O(n), including O(n) recursion stack on a path.

#include <cassert>
#include <utility>
#include <vector>

namespace noya {

/// @brief Sum edge-affine path transforms from every possible root. For an
/// oriented edge x-y, the message sent by y is the sum of path values whose
/// destinations lie on y's side after deleting the edge. Applying bx+c to all
/// of them gives b times that message plus c times the side size. One postorder
/// pass builds child-side messages; one preorder reroot pass supplies the
/// complementary parent-side message.
template <class T>
std::vector<T> tree_path_affine_sums(
    const std::vector<T> &vertex_values,
    const std::vector<std::pair<int, int>> &edge_endpoints,
    const std::vector<std::pair<T, T>> &edge_affine) {
  int n = int(vertex_values.size());
  assert(edge_endpoints.size() == edge_affine.size());
  assert(int(edge_endpoints.size()) == std::max(0, n - 1));
  if (n == 0) {
    return {};
  }
  struct edge {
    int to;
    T multiplier;
    T addition;
  };
  std::vector<std::vector<edge>> graph(n);
  for (int i = 0; i < n - 1; i++) {
    auto [first, second] = edge_endpoints[i];
    auto [multiplier, addition] = edge_affine[i];
    graph[first].push_back({second, multiplier, addition});
    graph[second].push_back({first, multiplier, addition});
  }

  std::vector<int> subtree_size(n);
  std::vector<T> downward(n);
  auto build = [&](auto &&self, int vertex, int parent) -> void {
    subtree_size[vertex] = 1;
    downward[vertex] = vertex_values[vertex];
    for (const edge &item : graph[vertex]) {
      if (item.to == parent) {
        continue;
      }
      self(self, item.to, vertex);
      subtree_size[vertex] += subtree_size[item.to];
      downward[vertex] += item.multiplier * downward[item.to] +
                          item.addition * T(subtree_size[item.to]);
    }
  };
  build(build, 0, -1);

  std::vector<T> answer(n);
  answer[0] = downward[0];
  auto reroot = [&](auto &&self, int vertex, int parent) -> void {
    for (const edge &item : graph[vertex]) {
      if (item.to == parent) {
        continue;
      }
      T child_contribution =
          item.multiplier * downward[item.to] +
          item.addition * T(subtree_size[item.to]);
      T parent_side_message = answer[vertex] - child_contribution;
      T parent_contribution =
          item.multiplier * parent_side_message +
          item.addition * T(n - subtree_size[item.to]);
      answer[item.to] = downward[item.to] + parent_contribution;
      self(self, item.to, vertex);
    }
  };
  reroot(reroot, 0, -1);
  return answer;
}

} // namespace noya

#endif // NOYA_TREE_PATH_AFFINE_SUM_HPP
#include <cassert>
#include <utility>
#include <vector>

/// @complexity Time: O(n).
/// Space: O(n), including O(n) recursion stack on a path.

namespace noya {

/// @brief Sum edge-affine path transforms from every possible root. For an
/// oriented edge x-y, the message sent by y is the sum of path values whose
/// destinations lie on y's side after deleting the edge. Applying bx+c to all
/// of them gives b times that message plus c times the side size. One postorder
/// pass builds child-side messages; one preorder reroot pass supplies the
/// complementary parent-side message.
template <class T>
std::vector<T> tree_path_affine_sums(
    const std::vector<T> &vertex_values,
    const std::vector<std::pair<int, int>> &edge_endpoints,
    const std::vector<std::pair<T, T>> &edge_affine) {
  int n = int(vertex_values.size());
  assert(edge_endpoints.size() == edge_affine.size());
  assert(int(edge_endpoints.size()) == std::max(0, n - 1));
  if (n == 0) {
    return {};
  }
  struct edge {
    int to;
    T multiplier;
    T addition;
  };
  std::vector<std::vector<edge>> graph(n);
  for (int i = 0; i < n - 1; i++) {
    auto [first, second] = edge_endpoints[i];
    auto [multiplier, addition] = edge_affine[i];
    graph[first].push_back({second, multiplier, addition});
    graph[second].push_back({first, multiplier, addition});
  }

  std::vector<int> subtree_size(n);
  std::vector<T> downward(n);
  auto build = [&](auto &&self, int vertex, int parent) -> void {
    subtree_size[vertex] = 1;
    downward[vertex] = vertex_values[vertex];
    for (const edge &item : graph[vertex]) {
      if (item.to == parent) {
        continue;
      }
      self(self, item.to, vertex);
      subtree_size[vertex] += subtree_size[item.to];
      downward[vertex] += item.multiplier * downward[item.to] +
                          item.addition * T(subtree_size[item.to]);
    }
  };
  build(build, 0, -1);

  std::vector<T> answer(n);
  answer[0] = downward[0];
  auto reroot = [&](auto &&self, int vertex, int parent) -> void {
    for (const edge &item : graph[vertex]) {
      if (item.to == parent) {
        continue;
      }
      T child_contribution =
          item.multiplier * downward[item.to] +
          item.addition * T(subtree_size[item.to]);
      T parent_side_message = answer[vertex] - child_contribution;
      T parent_contribution =
          item.multiplier * parent_side_message +
          item.addition * T(n - subtree_size[item.to]);
      answer[item.to] = downward[item.to] + parent_contribution;
      self(self, item.to, vertex);
    }
  };
  reroot(reroot, 0, -1);
  return answer;
}

} // namespace noya