chore: import upstream snapshot with attribution
Awesome CI Workflow / Code Formatter (push) Has been cancelled
Awesome CI Workflow / Compile checks (macOS-latest) (push) Has been cancelled
Awesome CI Workflow / Compile checks (ubuntu-latest) (push) Has been cancelled
Awesome CI Workflow / Compile checks (windows-latest) (push) Has been cancelled
Awesome CI Workflow / Code Formatter (push) Has been cancelled
Awesome CI Workflow / Compile checks (macOS-latest) (push) Has been cancelled
Awesome CI Workflow / Compile checks (ubuntu-latest) (push) Has been cancelled
Awesome CI Workflow / Compile checks (windows-latest) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
# If necessary, use the RELATIVE flag, otherwise each source file may be listed
|
||||
# with full pathname. RELATIVE may makes it easier to extract an executable name
|
||||
# automatically.
|
||||
file( GLOB APP_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp )
|
||||
# file( GLOB APP_SOURCES ${CMAKE_SOURCE_DIR}/*.c )
|
||||
# AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR} APP_SOURCES)
|
||||
foreach( testsourcefile ${APP_SOURCES} )
|
||||
# I used a simple string replace, to cut off .cpp.
|
||||
string( REPLACE ".cpp" "" testname ${testsourcefile} )
|
||||
add_executable( ${testname} ${testsourcefile} )
|
||||
|
||||
set_target_properties(${testname} PROPERTIES LINKER_LANGUAGE CXX)
|
||||
if(OpenMP_CXX_FOUND)
|
||||
target_link_libraries(${testname} OpenMP::OpenMP_CXX)
|
||||
endif()
|
||||
install(TARGETS ${testname} DESTINATION "bin/range_queries")
|
||||
|
||||
endforeach( testsourcefile ${APP_SOURCES} )
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief [Fenwick Tree](https://en.wikipedia.org/wiki/Fenwick_tree) algorithm
|
||||
* implementation
|
||||
* @details
|
||||
* _From Wikipedia, the free encyclopedia._
|
||||
*
|
||||
* A Fenwick tree or binary indexed tree (BIT) is a clever implementation of a
|
||||
* datastructure called bionomal tree. It can update values and solve range
|
||||
* queries with operations that is; commulative, associative and has an
|
||||
* inverse for this type of element. It can also solve immutable range queries
|
||||
* (min/max), when operations only are associative over this type of element.
|
||||
* Some of these restrictions can be removed, by storing redunant information;
|
||||
* like in max/min range queries.
|
||||
*
|
||||
* @author [Mateusz Grzegorzek](https://github.com/mateusz-grzegorzek)
|
||||
* @author [David Leal](https://github.com/Panquesito7)
|
||||
*/
|
||||
|
||||
#include <cassert> /// for assert
|
||||
#include <iostream> /// for IO operations
|
||||
#include <vector> /// for std::vector
|
||||
|
||||
/**
|
||||
* @namespace
|
||||
* @brief Range Queries
|
||||
*/
|
||||
namespace range_queries {
|
||||
/**
|
||||
* @brief The class that initializes the Fenwick Tree.
|
||||
*/
|
||||
class fenwick_tree {
|
||||
size_t n = 0; ///< No. of elements present in input array
|
||||
std::vector<int> bit{}; ///< Array that represents Binary Indexed Tree.
|
||||
|
||||
/**
|
||||
* @brief Returns the highest power of two which is not more than `x`.
|
||||
* @param x Index of element in original array.
|
||||
* @return Offset of index.
|
||||
*/
|
||||
inline int offset(int x) { return (x & (-x)); }
|
||||
public:
|
||||
/**
|
||||
* @brief Class Constructor
|
||||
* @tparam T the type of the array
|
||||
* @param[in] arr Input array for which prefix sum is evaluated.
|
||||
* @return void
|
||||
*/
|
||||
template <typename T>
|
||||
explicit fenwick_tree(const std::vector<T>& arr) : n(arr.size()) {
|
||||
bit.assign(n + 1, 0);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
update(i, arr[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Class Constructor
|
||||
* @tparam T the type of the variable
|
||||
* @param[in] x Size of array that represents Binary Indexed Tree.
|
||||
* @return void
|
||||
*/
|
||||
template <typename T>
|
||||
explicit fenwick_tree(T x) : n(x) { bit.assign(n + 1, 0); }
|
||||
|
||||
/**
|
||||
* @brief Updates the value of an element in original array and
|
||||
* accordingly updates the values in BIT array.
|
||||
* @tparam T the type of the variables
|
||||
* @param id Index of element in original array.
|
||||
* @param val Value with which element's value is updated.
|
||||
* @return void
|
||||
*/
|
||||
template <typename T>
|
||||
void update(T id, T val) {
|
||||
id++;
|
||||
while (id <= n) {
|
||||
bit[id] += val;
|
||||
id += offset(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the sum of elements in range from 0 to ID.
|
||||
* @tparam T the type of the variables
|
||||
* @param id Index in original array up to which sum is evaluated.
|
||||
* @return Sum of elements in range from 0 to id.
|
||||
*/
|
||||
template <typename T>
|
||||
int sum(T id) {
|
||||
id++;
|
||||
T res = 0;
|
||||
while (id > 0) {
|
||||
res += bit[id];
|
||||
id -= offset(id);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the prefix sum in range from L to R.
|
||||
* @param l Left index of range.
|
||||
* @param r Right index of range.
|
||||
* @return Sum of elements in range from L to R.
|
||||
*/
|
||||
int sum_range(int l, int r) { return sum(r) - sum(l - 1); }
|
||||
};
|
||||
} // namespace range_queries
|
||||
|
||||
/**
|
||||
* @brief Self-test implementations
|
||||
* @returns void
|
||||
*/
|
||||
static void tests() {
|
||||
std::vector<int> arr = {1, 2, 3, 4, 5};
|
||||
range_queries::fenwick_tree fenwick_tree(arr);
|
||||
|
||||
assert(fenwick_tree.sum_range(0, 0) == 1);
|
||||
assert(fenwick_tree.sum_range(0, 1) == 3);
|
||||
assert(fenwick_tree.sum_range(0, 2) == 6);
|
||||
assert(fenwick_tree.sum_range(0, 3) == 10);
|
||||
assert(fenwick_tree.sum_range(0, 4) == 15);
|
||||
|
||||
fenwick_tree.update(0, 6);
|
||||
std::cout << "All tests have successfully passed!\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Main function
|
||||
* @returns 0 on exit
|
||||
*/
|
||||
int main() {
|
||||
tests(); // run self-test implementations
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,646 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief [Heavy Light
|
||||
* Decomposition](https://en.wikipedia.org/wiki/Heavy_path_decomposition)
|
||||
* implementation
|
||||
* @author [Aniruthan R](https://github.com/aneee004)
|
||||
*
|
||||
* @details
|
||||
* Heavy-Light Decomposition is a technique on trees, that supports the
|
||||
* following:
|
||||
* 1. Update node s, with a value v
|
||||
* 2. Return the (sum) of all node values on the simple path from a to b
|
||||
* (sum) can also be replced with XOR, OR, AND, min, or max
|
||||
*
|
||||
* The update is done in O(log n) time, and
|
||||
* the query is done in O(log^2 n) time with HLD
|
||||
* where, n is the number of nodes
|
||||
*
|
||||
* The template type is the data type of the value stored in the nodes.
|
||||
* If a non-primitive data-type is used as a template,
|
||||
* the coressponding operators must be overloaded.
|
||||
*
|
||||
* An HLD object can only be created with a constant number of nodes, and
|
||||
* it cannot be changed later. Creaty an empty instance is not supported.
|
||||
*
|
||||
* To start answering updates and queries,
|
||||
* 1. Create an instance of HLD<X> object (obj), with the required data type.
|
||||
* 2. Read in the edge/parent information and update it with obj.add_edge().
|
||||
* Note: The edges addes must be 0 indexed.
|
||||
* 3. Create a vector with initial node values, and call set_node_val() with it.
|
||||
* 4. Call obj.init() to populate the required information for supporting
|
||||
* operations.
|
||||
* 5. Call obj.update(node, new_val), to update the value at index 'node' to the
|
||||
* new value. Note: node must be 0 indexed
|
||||
* 6. Call obj.query(a, b) to get the (sum) of node values in the simple path
|
||||
* from a to b. Note: a and b, must be 0 indexed.
|
||||
*
|
||||
* Sample I/O at the bottom.
|
||||
* @todo Support edge weight queries, by storing the edge weight value in it's
|
||||
* child algorithm verified by testing in CSES path queries:
|
||||
* https://cses.fi/problemset/task/1138
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* @namespace range_queries
|
||||
* @brief Algorithms and Data Structures that support range queries and updates.
|
||||
*/
|
||||
namespace range_queries {
|
||||
/**
|
||||
* @namespace heavy_light_decomposition
|
||||
* @brief Heavy light decomposition algorithm
|
||||
*/
|
||||
namespace heavy_light_decomposition {
|
||||
/**
|
||||
* @brief A Basic Tree, which supports binary lifting
|
||||
* @tparam the data type of the values stored in the tree nodes
|
||||
* @details Deleting the default constructor
|
||||
* An instance can only be created with the number of nodes
|
||||
* Defaults:
|
||||
* t_node indexing are zero based
|
||||
* t_root is 0
|
||||
* depth of root_node is 0
|
||||
* Supports:
|
||||
* lift :- lift a node k units up the tree
|
||||
* kth_ancestor :- returns the kth ancestor
|
||||
* lca :- returns the least common ancestor
|
||||
*/
|
||||
template <typename X>
|
||||
class Tree {
|
||||
//
|
||||
|
||||
private:
|
||||
std::vector<std::list<int>>
|
||||
t_adj; ///< an adjacency list to stores the tree edges
|
||||
const int t_nodes, ///< number of nodes
|
||||
t_maxlift; ///< maximum possible height of the tree
|
||||
std::vector<std::vector<int>>
|
||||
t_par; ///< a matrix to store every node's 2^kth parent
|
||||
std::vector<int> t_depth, ///< a vector to store the depth of a node,
|
||||
t_size; ///< a vector to store the subtree size rooted at node
|
||||
|
||||
int t_root; ///< the root of the tree
|
||||
std::vector<X> t_val; ///< values of nodes
|
||||
template <typename T>
|
||||
friend class HLD;
|
||||
|
||||
/**
|
||||
* @brief Utility function to compute sub-tree sizes
|
||||
* @param u current dfs node
|
||||
* @param p the parent of node @param u
|
||||
* @returns void
|
||||
*/
|
||||
void dfs_size(int u, int p = -1) {
|
||||
for (const int &v : t_adj[u]) {
|
||||
if (v ^ p) {
|
||||
dfs_size(v, u);
|
||||
t_size[u] += t_size[v];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Utility function to populate the t_par vector
|
||||
* @param u current dfs node
|
||||
* @param p the parent of node u
|
||||
* @returns void
|
||||
*/
|
||||
void dfs_lca(int u, int p = -1) {
|
||||
t_par[u][0] = p;
|
||||
if (p != -1) {
|
||||
t_depth[u] = 1 + t_depth[p];
|
||||
}
|
||||
for (int k = 1; k < t_maxlift; k++) {
|
||||
if (t_par[u][k - 1] != -1) {
|
||||
t_par[u][k] = t_par[t_par[u][k - 1]][k - 1];
|
||||
}
|
||||
}
|
||||
|
||||
for (const int &v : t_adj[u]) {
|
||||
if (v ^ p) {
|
||||
dfs_lca(v, u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Class parameterized constructor, resizes the and initializes the
|
||||
* data members
|
||||
* @param nodes the total number of nodes in the tree
|
||||
*/
|
||||
explicit Tree(int nodes)
|
||||
: t_nodes(nodes), t_maxlift(static_cast<int>(floor(log2(nodes))) + 1) {
|
||||
/* Initialize and resize all the vectors */
|
||||
t_root = 0; /* Default */
|
||||
t_adj.resize(t_nodes);
|
||||
t_par.assign(t_nodes, std::vector<int>(t_maxlift, -1));
|
||||
t_depth.assign(t_nodes, 0);
|
||||
t_size.assign(t_nodes, 1);
|
||||
t_val.resize(t_nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds an undirected edge from node u to node v in the tree
|
||||
* @param u the node where the edge is from
|
||||
* @param v the node where the edge is to
|
||||
* @returns void
|
||||
*/
|
||||
void add_edge(const int u, const int v) {
|
||||
t_adj[u].push_back(v);
|
||||
t_adj[v].push_back(u);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the root for the tree
|
||||
* @param new_root the new root
|
||||
* @returns void
|
||||
*/
|
||||
void change_root(int new_root) { t_root = new_root; }
|
||||
|
||||
/**
|
||||
* @brief Set the values for all the nodes
|
||||
* @param node_val a vector of size n, with all the node values where, n is
|
||||
* the number of nodes
|
||||
* @returns void
|
||||
*/
|
||||
void set_node_val(const std::vector<X> &node_val) {
|
||||
assert(static_cast<int>(node_val.size()) == t_nodes);
|
||||
t_val = node_val;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This function must be called after the tree adjacency list and
|
||||
* node values are populated The function initializes the required
|
||||
* parameters, and populates the segment tree
|
||||
* @returns void
|
||||
*/
|
||||
void init() {
|
||||
assert(t_nodes > 0);
|
||||
dfs_size(t_root);
|
||||
dfs_lca(t_root);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The function lifts a node, k units up the tree.
|
||||
* The lifting is done in place, and the result is stored in the address
|
||||
* pointed by p.
|
||||
* @param p a pointer to the variable that stores the node id
|
||||
* @param dist the distance to move up the tree
|
||||
* @returns void
|
||||
*/
|
||||
void lift(int *const p, int dist) {
|
||||
for (int k = 0; k < t_maxlift; k++) {
|
||||
if (*p == -1) {
|
||||
return;
|
||||
}
|
||||
if (dist & 1) {
|
||||
*p = t_par[*p][k];
|
||||
}
|
||||
dist >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The function returns the kth ancestor of a node
|
||||
* @param p the node id whose kth ancestor is to be found
|
||||
* @param dist the distance to move up the tree
|
||||
* @returns the kth ancestor of node
|
||||
*/
|
||||
int kth_ancestor(int p, const int &dist) {
|
||||
lift(&p, dist);
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The function returns the least common ancestor of two nodes
|
||||
* @param a node id_1
|
||||
* @param b node id_2
|
||||
* @returns the least common ancestor of node a, and node b
|
||||
*/
|
||||
int lca(int a, int b) {
|
||||
assert(a >= 0 and b >= 0 and a < t_nodes and b < t_nodes);
|
||||
if (t_depth[a] > t_depth[b]) {
|
||||
lift(&a, t_depth[a] - t_depth[b]);
|
||||
}
|
||||
if (t_depth[b] > t_depth[a]) {
|
||||
lift(&b, t_depth[b] - t_depth[a]);
|
||||
}
|
||||
if (a == b) {
|
||||
return a;
|
||||
}
|
||||
for (int k = t_maxlift - 1; k >= 0; k--) {
|
||||
if (t_par[a][k] != t_par[b][k]) {
|
||||
a = t_par[a][k];
|
||||
b = t_par[b][k];
|
||||
}
|
||||
}
|
||||
return t_par[a][0];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Segment Tree, to store heavy chains
|
||||
* @tparam the data type of the values stored in the tree nodes
|
||||
*/
|
||||
template <typename X>
|
||||
class SG {
|
||||
private:
|
||||
/**
|
||||
* @brief Everything here is private,
|
||||
* and can only be accessed through the methods,
|
||||
* in the derived class (HLD)
|
||||
*/
|
||||
|
||||
std::vector<X> s_tree; ///< the segment tree, stored as a vector
|
||||
int s_size; ///< number of leaves in the segment tree
|
||||
X sret_init = 0; ///< inital query return value
|
||||
template <typename T>
|
||||
friend class HLD;
|
||||
|
||||
/**
|
||||
* @brief Function that specifies the type of operation involved when
|
||||
* segments are combined
|
||||
* @param lhs the left segment
|
||||
* @param rhs the right segment
|
||||
* @returns the combined result
|
||||
*/
|
||||
X combine(X lhs, X rhs) { return lhs + rhs; }
|
||||
|
||||
/**
|
||||
* @brief Class parameterized constructor. Resizes the and initilizes the
|
||||
* data members.
|
||||
* @param nodes the total number of nodes in the tree
|
||||
* @returns void
|
||||
*/
|
||||
explicit SG(int size) {
|
||||
s_size = size;
|
||||
s_tree.assign(2 * s_size, 0ll);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the value at a node
|
||||
* @param p the node to be udpated
|
||||
* @param v the update value
|
||||
* @returns void
|
||||
*/
|
||||
void update(int p, X v) {
|
||||
for (p += s_size; p > 0; p >>= 1) {
|
||||
s_tree[p] += v;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Make a range query from node label l to node label r
|
||||
* @param l node label where the path starts
|
||||
* @param r node label where the path ends
|
||||
* @returns void
|
||||
*/
|
||||
X query(int l, int r) {
|
||||
X lhs = sret_init, rhs = sret_init;
|
||||
for (l += s_size, r += s_size + 1; l < r; l >>= 1, r >>= 1) {
|
||||
if (l & 1) {
|
||||
lhs = combine(lhs, s_tree[l++]);
|
||||
}
|
||||
if (r & 1) {
|
||||
rhs = combine(s_tree[--r], rhs);
|
||||
}
|
||||
}
|
||||
return combine(lhs, rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the initialization for the query data type, based on
|
||||
* requirement
|
||||
*
|
||||
* @details
|
||||
* Change the sret_init, based on requirement:
|
||||
* * Sum Query: 0 (Default)
|
||||
* * XOR Query: 0 (Default)
|
||||
* * Min Query: Infinity
|
||||
* * Max Query: -Infinity
|
||||
* @param new_sret_init the new init
|
||||
*/
|
||||
void set_sret_init(X new_sret_init) { sret_init = new_sret_init; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The Heavy-Light Decomposition class
|
||||
* @tparam the data type of the values stored in the tree nodes
|
||||
*/
|
||||
template <typename X>
|
||||
class HLD : public Tree<X>, public SG<X> {
|
||||
private:
|
||||
int label; ///< utility member to assign labels in dfs_labels()
|
||||
std::vector<int> h_label, ///< stores the label of a node
|
||||
h_heavychlid, ///< stores the heavy child of a node
|
||||
h_parent; ///< stores the top of the heavy chain from a node
|
||||
|
||||
/**
|
||||
* @brief Utility function to assign heavy child to each node (-1 for a leaf
|
||||
* node)
|
||||
* @param u current dfs node
|
||||
* @param p the parent of node u
|
||||
* @returns void
|
||||
*/
|
||||
void dfs_hc(int u, int p = -1) {
|
||||
int hc_size = -1, hc_id = -1;
|
||||
for (const int &v : Tree<X>::t_adj[u]) {
|
||||
if (v ^ p) {
|
||||
dfs_hc(v, u);
|
||||
if (Tree<X>::t_size[v] > hc_size) {
|
||||
hc_size = Tree<X>::t_size[v];
|
||||
hc_id = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
h_heavychlid[u] = hc_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Utility function to assign highest parent that can be reached
|
||||
* though heavy chains
|
||||
* @param u current dfs node
|
||||
* @param p the parent of node u
|
||||
* @returns void
|
||||
*/
|
||||
void dfs_par(int u, int p = -1) {
|
||||
if (h_heavychlid[u] != -1) {
|
||||
h_parent[h_heavychlid[u]] = h_parent[u];
|
||||
dfs_par(h_heavychlid[u], u);
|
||||
}
|
||||
for (const int &v : Tree<X>::t_adj[u]) {
|
||||
if (v ^ p and v ^ h_heavychlid[u]) {
|
||||
dfs_par(v, u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Utility function to lable the nodes so that heavy chains have a
|
||||
* contigous lable
|
||||
* @param u current dfs node
|
||||
* @param p the parent of node u
|
||||
* @returns void
|
||||
*/
|
||||
void dfs_labels(int u, int p = -1) {
|
||||
h_label[u] = label++;
|
||||
if (h_heavychlid[u] != -1) {
|
||||
dfs_labels(h_heavychlid[u], u);
|
||||
}
|
||||
for (const int &v : Tree<X>::t_adj[u]) {
|
||||
if (v ^ p and v ^ h_heavychlid[u]) {
|
||||
dfs_labels(v, u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Utility function to break down a path query into two chain queries
|
||||
* @param a node where the path starts
|
||||
* @param b node where the path ends
|
||||
* a and b must belong to a single root to leaf chain
|
||||
* @returns the sum of ndoe values in the simple path from a to b
|
||||
*/
|
||||
X chain_query(int a, int b) {
|
||||
X ret = SG<X>::sret_init;
|
||||
if (Tree<X>::t_depth[a] < Tree<X>::t_depth[b]) {
|
||||
std::swap(a, b);
|
||||
}
|
||||
while (Tree<X>::t_depth[a] >= Tree<X>::t_depth[b]) {
|
||||
int l = h_label[h_parent[a]];
|
||||
int r = h_label[a];
|
||||
if (Tree<X>::t_depth[h_parent[a]] < Tree<X>::t_depth[b]) {
|
||||
l += Tree<X>::t_depth[b] - Tree<X>::t_depth[h_parent[a]];
|
||||
}
|
||||
ret = SG<X>::combine(ret, SG<X>::query(l, r));
|
||||
a = Tree<X>::t_par[h_parent[a]][0];
|
||||
if (a == -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Class parameterized constructor. Resizes the and initilizes the
|
||||
* data members.
|
||||
* @param nodes the total number of nodes in the tree
|
||||
*/
|
||||
explicit HLD(int nodes) : Tree<X>(nodes), SG<X>(nodes) {
|
||||
/* Initialization and resize vectors */
|
||||
label = 0;
|
||||
h_label.assign(Tree<X>::t_nodes, -1);
|
||||
h_heavychlid.assign(Tree<X>::t_nodes, -1);
|
||||
h_parent.resize(Tree<X>::t_nodes);
|
||||
iota(h_parent.begin(), h_parent.end(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This function must be called after the tree adjacency list and
|
||||
* node values are populated The function initializes the required
|
||||
* parametes, and populates the segment tree
|
||||
* @returns void
|
||||
*/
|
||||
void init() {
|
||||
Tree<X>::init();
|
||||
|
||||
// Fill the heavy child, greatest parent, and labels
|
||||
label = 0;
|
||||
dfs_hc(Tree<X>::t_root);
|
||||
dfs_par(Tree<X>::t_root);
|
||||
dfs_labels(Tree<X>::t_root);
|
||||
|
||||
// Segment Tree Initialization
|
||||
for (int i = 0; i < Tree<X>::t_nodes; i++) {
|
||||
SG<X>::s_tree[h_label[i] + Tree<X>::t_nodes] = Tree<X>::t_val[i];
|
||||
}
|
||||
for (int i = Tree<X>::t_nodes - 1; i > 0; i--) {
|
||||
SG<X>::s_tree[i] = SG<X>::combine(SG<X>::s_tree[i << 1],
|
||||
SG<X>::s_tree[i << 1 | 1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This function updates the value at node with val
|
||||
* @param node the node where the update is done
|
||||
* @param val the value that is being updated
|
||||
* @returns void
|
||||
*/
|
||||
void update(int node, X val) {
|
||||
X diff = val - Tree<X>::t_val[node];
|
||||
SG<X>::update(h_label[node], diff);
|
||||
Tree<X>::t_val[node] = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This function returns the sum of node values in the simple path
|
||||
* from from node_1 to node_2
|
||||
* @param a the node where the simple path starts
|
||||
* @param b the node where the simple path ends
|
||||
* (parameters are interchangeable, i.e., the function is commutative)
|
||||
* @returns the sum of node values in the simple path from a to b
|
||||
*/
|
||||
X query(int a, int b) {
|
||||
int lc = Tree<X>::lca(a, b);
|
||||
X ret = SG<X>::sret_init;
|
||||
assert(lc != -1);
|
||||
ret += chain_query(a, lc);
|
||||
ret += chain_query(b, lc);
|
||||
return ret - Tree<X>::t_val[lc];
|
||||
}
|
||||
};
|
||||
} // namespace heavy_light_decomposition
|
||||
} // namespace range_queries
|
||||
|
||||
/**
|
||||
* Test implementations
|
||||
* @returns none
|
||||
*/
|
||||
static void test_1() {
|
||||
std::cout << "Test 1:\n";
|
||||
|
||||
// Test details
|
||||
int n = 5;
|
||||
std::vector<int64_t> node_values = {4, 2, 5, 2, 1};
|
||||
std::vector<std::vector<int>> edges = {{1, 2}, {1, 3}, {3, 4}, {3, 5}};
|
||||
std::vector<std::vector<int>> queries = {
|
||||
{2, 1, 4},
|
||||
{1, 3, 2},
|
||||
{2, 1, 4},
|
||||
};
|
||||
std::vector<int> expected_result = {11, 8};
|
||||
std::vector<int> code_result;
|
||||
|
||||
range_queries::heavy_light_decomposition::HLD<int64_t> hld(n);
|
||||
hld.set_node_val(node_values);
|
||||
for (int i = 0; i < n - 1; i++) {
|
||||
int u = edges[i][0], v = edges[i][1];
|
||||
hld.add_edge(u - 1, v - 1);
|
||||
}
|
||||
hld.init();
|
||||
for (const auto &q : queries) {
|
||||
int type = q[0];
|
||||
if (type == 1) {
|
||||
int p = q[1], x = q[2];
|
||||
hld.update(p - 1, x);
|
||||
} else if (type == 2) {
|
||||
int a = q[1], b = q[2];
|
||||
code_result.push_back(hld.query(a - 1, b - 1));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < static_cast<int>(expected_result.size()); i++) {
|
||||
assert(expected_result[i] == code_result[i]);
|
||||
}
|
||||
std::cout << "\nTest 1 passed!\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Second test implementations
|
||||
* @returns void
|
||||
*/
|
||||
static void test_2() {
|
||||
std::cout << "Test 2:\n";
|
||||
|
||||
// Test details (Bamboo)
|
||||
int n = 10;
|
||||
std::vector<int64_t> node_values = {1, 8, 6, 8, 6, 2, 9, 2, 3, 2};
|
||||
std::vector<std::vector<int>> edges = {{10, 5}, {6, 2}, {10, 7},
|
||||
{5, 2}, {3, 9}, {8, 3},
|
||||
{1, 4}, {6, 4}, {8, 7}};
|
||||
std::vector<std::vector<int>> queries = {
|
||||
{2, 1, 10}, {2, 1, 6}, {1, 3, 4}, {2, 1, 9}, {1, 5, 3},
|
||||
{1, 7, 8}, {2, 1, 4}, {2, 1, 8}, {1, 1, 4}, {1, 2, 7}};
|
||||
std::vector<int> expected_result = {27, 11, 45, 9, 34};
|
||||
std::vector<int> code_result;
|
||||
|
||||
range_queries::heavy_light_decomposition::HLD<int64_t> hld(n);
|
||||
hld.set_node_val(node_values);
|
||||
for (int i = 0; i < n - 1; i++) {
|
||||
int u = edges[i][0], v = edges[i][1];
|
||||
hld.add_edge(u - 1, v - 1);
|
||||
}
|
||||
hld.init();
|
||||
for (const auto &q : queries) {
|
||||
int type = q[0];
|
||||
if (type == 1) {
|
||||
int p = q[1], x = q[2];
|
||||
hld.update(p - 1, x);
|
||||
} else if (type == 2) {
|
||||
int a = q[1], b = q[2];
|
||||
code_result.push_back(hld.query(a - 1, b - 1));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < static_cast<int>(expected_result.size()); i++) {
|
||||
assert(expected_result[i] == code_result[i]);
|
||||
}
|
||||
std::cout << "\nTest2 passed!\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Third test implementations
|
||||
* @returns void
|
||||
*/
|
||||
static void test_3() {
|
||||
std::cout << "Test 3:\n";
|
||||
|
||||
// Test details
|
||||
int n = 8;
|
||||
std::vector<int64_t> node_values = {1, 8, 6, 8, 6, 2, 9, 2};
|
||||
std::vector<std::vector<int>> edges = {{1, 2}, {2, 3}, {3, 4}, {1, 5},
|
||||
{6, 3}, {7, 5}, {8, 7}};
|
||||
std::vector<std::vector<int>> queries = {
|
||||
{2, 6, 8}, {2, 3, 6}, {1, 3, 4}, {2, 7, 1}, {1, 5, 3},
|
||||
{1, 7, 8}, {2, 6, 4}, {2, 7, 8}, {1, 1, 4}, {1, 2, 7}};
|
||||
std::vector<int> expected_result = {34, 8, 16, 14, 10};
|
||||
std::vector<int> code_result;
|
||||
|
||||
range_queries::heavy_light_decomposition::HLD<int64_t> hld(n);
|
||||
hld.set_node_val(node_values);
|
||||
for (int i = 0; i < n - 1; i++) {
|
||||
int u = edges[i][0], v = edges[i][1];
|
||||
hld.add_edge(u - 1, v - 1);
|
||||
}
|
||||
hld.init();
|
||||
for (const auto &q : queries) {
|
||||
int type = q[0];
|
||||
if (type == 1) {
|
||||
int p = q[1], x = q[2];
|
||||
hld.update(p - 1, x);
|
||||
} else if (type == 2) {
|
||||
int a = q[1], b = q[2];
|
||||
code_result.push_back(hld.query(a - 1, b - 1));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < static_cast<int>(expected_result.size()); i++) {
|
||||
assert(expected_result[i] == code_result[i]);
|
||||
}
|
||||
std::cout << "\nTest3 passed!\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function
|
||||
*/
|
||||
int main() {
|
||||
test_1();
|
||||
test_2();
|
||||
test_3();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
const int N = 1e6 + 5;
|
||||
int a[N], bucket[N], cnt[N];
|
||||
int bucket_size;
|
||||
struct query {
|
||||
int l, r, i;
|
||||
} q[N];
|
||||
int ans = 0;
|
||||
|
||||
void add(int index) {
|
||||
cnt[a[index]]++;
|
||||
if (cnt[a[index]] == 1)
|
||||
ans++;
|
||||
}
|
||||
void remove(int index) {
|
||||
cnt[a[index]]--;
|
||||
if (cnt[a[index]] == 0)
|
||||
ans--;
|
||||
}
|
||||
|
||||
bool mycmp(query x, query y) {
|
||||
if (x.l / bucket_size != y.l / bucket_size)
|
||||
return x.l / bucket_size < y.l / bucket_size;
|
||||
return x.r < y.r;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int n, t, i;
|
||||
scanf("%d", &n);
|
||||
for (i = 0; i < n; i++) scanf("%d", &a[i]);
|
||||
bucket_size = ceil(sqrt(n));
|
||||
scanf("%d", &t);
|
||||
for (i = 0; i < t; i++) {
|
||||
scanf("%d %d", &q[i].l, &q[i].r);
|
||||
q[i].l--;
|
||||
q[i].r--;
|
||||
q[i].i = i;
|
||||
}
|
||||
sort(q, q + t, mycmp);
|
||||
int left = 0, right = 0;
|
||||
for (i = 0; i < t; i++) {
|
||||
int L = q[i].l, R = q[i].r;
|
||||
while (left < L) {
|
||||
remove(left);
|
||||
left++;
|
||||
}
|
||||
while (left > L) {
|
||||
add(left - 1);
|
||||
left--;
|
||||
}
|
||||
while (right <= R) {
|
||||
add(right);
|
||||
right++;
|
||||
}
|
||||
while (right > R + 1) {
|
||||
remove(right - 1);
|
||||
right--;
|
||||
}
|
||||
bucket[q[i].i] = ans;
|
||||
}
|
||||
for (i = 0; i < t; i++) printf("%d\n", bucket[i]);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief [Persistent segment tree with range updates (lazy
|
||||
* propagation)](https://en.wikipedia.org/wiki/Persistent_data_structure)
|
||||
*
|
||||
* @details
|
||||
* A normal segment tree facilitates making point updates and range queries in
|
||||
* logarithmic time. Lazy propagation preserves the logarithmic time with range
|
||||
* updates. So, a segment tree with lazy propagation enables doing range updates
|
||||
* and range queries in logarithmic time, but it doesn't save any information
|
||||
* about itself before the last update. A persistent data structure always
|
||||
* preserves the previous version of itself when it is modified. That is, a new
|
||||
* version of the segment tree is generated after every update. It saves all
|
||||
* previous versions of itself (before every update) to facilitate doing range
|
||||
* queries in any version. More memory is used ,but the logarithmic time is
|
||||
* preserved because the new version points to the same nodes, that the previous
|
||||
* version points to, that are not affected by the update. That is, only the
|
||||
* nodes that are affected by the update and their ancestors are copied. The
|
||||
* rest is copied using lazy propagation in the next queries. Thus preserving
|
||||
* the logarithmic time because the number of nodes copied after any update is
|
||||
* logarithmic.
|
||||
*
|
||||
* @author [Magdy Sedra](https://github.com/MSedra)
|
||||
*/
|
||||
#include <cstdint> /// for std::uint32_t
|
||||
#include <iostream> /// for IO operations
|
||||
#include <memory> /// to manage dynamic memory
|
||||
#include <vector> /// for std::vector
|
||||
|
||||
/**
|
||||
* @namespace range_queries
|
||||
* @brief Range queries algorithms
|
||||
*/
|
||||
namespace range_queries {
|
||||
|
||||
/**
|
||||
* @brief Range query here is range sum, but the code can be modified to make
|
||||
* different queries like range max or min.
|
||||
*/
|
||||
class perSegTree {
|
||||
private:
|
||||
class Node {
|
||||
public:
|
||||
std::shared_ptr<Node> left = nullptr; /// pointer to the left node
|
||||
std::shared_ptr<Node> right = nullptr; /// pointer to the right node
|
||||
int64_t val = 0,
|
||||
prop = 0; /// val is the value of the node (here equals to the
|
||||
/// sum of the leaf nodes children of that node),
|
||||
/// prop is the value to be propagated/added to all
|
||||
/// the leaf nodes children of that node
|
||||
};
|
||||
|
||||
uint32_t n = 0; /// number of elements/leaf nodes in the segment tree
|
||||
std::vector<std::shared_ptr<Node>>
|
||||
ptrs{}; /// ptrs[i] holds a root pointer to the segment tree after the
|
||||
/// ith update. ptrs[0] holds a root pointer to the segment
|
||||
/// tree before any updates
|
||||
std::vector<int64_t> vec{}; /// values of the leaf nodes that the segment
|
||||
/// tree will be constructed with
|
||||
|
||||
/**
|
||||
* @brief Creating a new node with the same values of curr node
|
||||
* @param curr node that would be copied
|
||||
* @returns the new node
|
||||
*/
|
||||
std::shared_ptr<Node> newKid(std::shared_ptr<Node> const &curr) {
|
||||
auto newNode = std::make_shared<Node>(Node());
|
||||
newNode->left = curr->left;
|
||||
newNode->right = curr->right;
|
||||
newNode->prop = curr->prop;
|
||||
newNode->val = curr->val;
|
||||
return newNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief If there is some value to be propagated to the passed node, value
|
||||
* is added to the node and the children of the node, if exist, are copied
|
||||
* and the propagated value is also added to them
|
||||
* @param i the left index of the range that the passed node holds its sum
|
||||
* @param j the right index of the range that the passed node holds its sum
|
||||
* @param curr pointer to the node to be propagated
|
||||
* @returns void
|
||||
*/
|
||||
void lazy(const uint32_t &i, const uint32_t &j,
|
||||
std::shared_ptr<Node> const &curr) {
|
||||
if (!curr->prop) {
|
||||
return;
|
||||
}
|
||||
curr->val += (j - i + 1) * curr->prop;
|
||||
if (i != j) {
|
||||
curr->left = newKid(curr->left);
|
||||
curr->right = newKid(curr->right);
|
||||
curr->left->prop += curr->prop;
|
||||
curr->right->prop += curr->prop;
|
||||
}
|
||||
curr->prop = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructing the segment tree with the early passed vector. Every
|
||||
* call creates a node to hold the sum of the given range, set its pointers
|
||||
* to the children, and set its value to the sum of the children's values
|
||||
* @param i the left index of the range that the created node holds its sum
|
||||
* @param j the right index of the range that the created node holds its sum
|
||||
* @returns pointer to the newly created node
|
||||
*/
|
||||
std::shared_ptr<Node> construct(const uint32_t &i, const uint32_t &j) {
|
||||
auto newNode = std::make_shared<Node>(Node());
|
||||
if (i == j) {
|
||||
newNode->val = vec[i];
|
||||
} else {
|
||||
uint32_t mid = i + (j - i) / 2;
|
||||
auto leftt = construct(i, mid);
|
||||
auto right = construct(mid + 1, j);
|
||||
newNode->val = leftt->val + right->val;
|
||||
newNode->left = leftt;
|
||||
newNode->right = right;
|
||||
}
|
||||
return newNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Doing range update, checking at every node if it has some value to
|
||||
* be propagated. All nodes affected by the update are copied and
|
||||
* propagation value is added to the leaf of them
|
||||
* @param i the left index of the range that the passed node holds its sum
|
||||
* @param j the right index of the range that the passed node holds its sum
|
||||
* @param l the left index of the range to be updated
|
||||
* @param r the right index of the range to be updated
|
||||
* @param value the value to be added to every element whose index x
|
||||
* satisfies l<=x<=r
|
||||
* @param curr pointer to the current node, which has value = the sum of
|
||||
* elements whose index x satisfies i<=x<=j
|
||||
* @returns pointer to the current newly created node
|
||||
*/
|
||||
std::shared_ptr<Node> update(const uint32_t &i, const uint32_t &j,
|
||||
const uint32_t &l, const uint32_t &r,
|
||||
const int64_t &value,
|
||||
std::shared_ptr<Node> const &curr) {
|
||||
lazy(i, j, curr);
|
||||
if (i >= l && j <= r) {
|
||||
std::shared_ptr<Node> newNode = newKid(curr);
|
||||
newNode->prop += value;
|
||||
lazy(i, j, newNode);
|
||||
return newNode;
|
||||
}
|
||||
if (i > r || j < l) {
|
||||
return curr;
|
||||
}
|
||||
auto newNode = std::make_shared<Node>(Node());
|
||||
uint32_t mid = i + (j - i) / 2;
|
||||
newNode->left = update(i, mid, l, r, value, curr->left);
|
||||
newNode->right = update(mid + 1, j, l, r, value, curr->right);
|
||||
newNode->val = newNode->left->val + newNode->right->val;
|
||||
return newNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Querying the range from index l to index r, checking at every node
|
||||
* if it has some value to be propagated. Current node's value is returned
|
||||
* if its range is completely inside the wanted range, else 0 is returned
|
||||
* @param i the left index of the range that the passed node holds its sum
|
||||
* @param j the right index of the range that the passed node holds its sum
|
||||
* @param l the left index of the range whose sum should be returned as a
|
||||
* result
|
||||
* @param r the right index of the range whose sum should be returned as a
|
||||
* result
|
||||
* @param curr pointer to the current node, which has value = the sum of
|
||||
* elements whose index x satisfies i<=x<=j
|
||||
* @returns sum of elements whose index x satisfies l<=x<=r
|
||||
*/
|
||||
int64_t query(const uint32_t &i, const uint32_t &j, const uint32_t &l,
|
||||
const uint32_t &r, std::shared_ptr<Node> const &curr) {
|
||||
lazy(i, j, curr);
|
||||
if (j < l || r < i) {
|
||||
return 0;
|
||||
}
|
||||
if (i >= l && j <= r) {
|
||||
return curr->val;
|
||||
}
|
||||
uint32_t mid = i + (j - i) / 2;
|
||||
return query(i, mid, l, r, curr->left) +
|
||||
query(mid + 1, j, l, r, curr->right);
|
||||
}
|
||||
|
||||
/**
|
||||
* public methods that can be used directly from outside the class. They
|
||||
* call the private functions that do all the work
|
||||
*/
|
||||
public:
|
||||
/**
|
||||
* @brief Constructing the segment tree with the values in the passed
|
||||
* vector. Returned root pointer is pushed in the pointers vector to have
|
||||
* access to the original version if the segment tree is updated
|
||||
* @param vec vector whose values will be used to build the segment tree
|
||||
* @returns void
|
||||
*/
|
||||
void construct(const std::vector<int64_t>
|
||||
&vec) // the segment tree will be built from the values
|
||||
// in "vec", "vec" is 0 indexed
|
||||
{
|
||||
if (vec.empty()) {
|
||||
return;
|
||||
}
|
||||
n = vec.size();
|
||||
this->vec = vec;
|
||||
auto root = construct(0, n - 1);
|
||||
ptrs.push_back(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Doing range update by passing the left and right indexes of the
|
||||
* range as well as the value to be added.
|
||||
* @param l the left index of the range to be updated
|
||||
* @param r the right index of the range to be updated
|
||||
* @param value the value to be added to every element whose index x
|
||||
* satisfies l<=x<=r
|
||||
* @returns void
|
||||
*/
|
||||
void update(const uint32_t &l, const uint32_t &r,
|
||||
const int64_t
|
||||
&value) // all elements from index "l" to index "r" would
|
||||
// by updated by "value", "l" and "r" are 0 indexed
|
||||
{
|
||||
ptrs.push_back(update(
|
||||
0, n - 1, l, r, value,
|
||||
ptrs[ptrs.size() -
|
||||
1])); // saving the root pointer to the new segment tree
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Querying the range from index l to index r, getting the sum of the
|
||||
* elements whose index x satisfies l<=x<=r
|
||||
* @param l the left index of the range whose sum should be returned as a
|
||||
* result
|
||||
* @param r the right index of the range whose sum should be returned as a
|
||||
* result
|
||||
* @param version the version to query on. If equals to 0, the original
|
||||
* segment tree will be queried
|
||||
* @returns sum of elements whose index x satisfies l<=x<=r
|
||||
*/
|
||||
int64_t query(
|
||||
const uint32_t &l, const uint32_t &r,
|
||||
const uint32_t
|
||||
&version) // querying the range from "l" to "r" in a segment tree
|
||||
// after "version" updates, "l" and "r" are 0 indexed
|
||||
{
|
||||
return query(0, n - 1, l, r, ptrs[version]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Getting the number of versions after updates so far which is equal
|
||||
* to the size of the pointers vector
|
||||
* @returns the number of versions
|
||||
*/
|
||||
uint32_t size() // returns the number of segment trees (versions) , the
|
||||
// number of updates done so far = returned value - 1
|
||||
// ,because one of the trees is the original segment tree
|
||||
{
|
||||
return ptrs.size();
|
||||
}
|
||||
};
|
||||
} // namespace range_queries
|
||||
|
||||
/**
|
||||
* @brief Test implementations
|
||||
* @returns void
|
||||
*/
|
||||
static void test() {
|
||||
std::vector<int64_t> arr = {-5, 2, 3, 11, -2, 7, 0, 1};
|
||||
range_queries::perSegTree tree;
|
||||
std::cout << "Elements before any updates are {";
|
||||
for (uint32_t i = 0; i < arr.size(); ++i) {
|
||||
std::cout << arr[i];
|
||||
if (i != arr.size() - 1) {
|
||||
std::cout << ",";
|
||||
}
|
||||
}
|
||||
std::cout << "}\n";
|
||||
tree.construct(
|
||||
arr); // constructing the original segment tree (version = 0)
|
||||
std::cout << "Querying range sum on version 0 from index 2 to 4 = 3+11-2 = "
|
||||
<< tree.query(2, 4, 0) << '\n';
|
||||
std::cout
|
||||
<< "Subtract 7 from all elements from index 1 to index 5 inclusive\n";
|
||||
tree.update(1, 5, -7); // subtracting 7 from index 1 to index 5
|
||||
std::cout << "Elements of the segment tree whose version = 1 (after 1 "
|
||||
"update) are {";
|
||||
for (uint32_t i = 0; i < arr.size(); ++i) {
|
||||
std::cout << tree.query(i, i, 1);
|
||||
if (i != arr.size() - 1) {
|
||||
std::cout << ",";
|
||||
}
|
||||
}
|
||||
std::cout << "}\n";
|
||||
std::cout << "Add 10 to all elements from index 0 to index 7 inclusive\n";
|
||||
tree.update(0, 7, 10); // adding 10 to all elements
|
||||
std::cout << "Elements of the segment tree whose version = 2 (after 2 "
|
||||
"updates) are {";
|
||||
for (uint32_t i = 0; i < arr.size(); ++i) {
|
||||
std::cout << tree.query(i, i, 2);
|
||||
if (i != arr.size() - 1) {
|
||||
std::cout << ",";
|
||||
}
|
||||
}
|
||||
std::cout << "}\n";
|
||||
std::cout << "Number of segment trees (versions) now = " << tree.size()
|
||||
<< '\n';
|
||||
std::cout << "Querying range sum on version 0 from index 3 to 5 = 11-2+7 = "
|
||||
<< tree.query(3, 5, 0) << '\n';
|
||||
std::cout << "Querying range sum on version 1 from index 3 to 5 = 4-9+0 = "
|
||||
<< tree.query(3, 5, 1) << '\n';
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Main function
|
||||
* @returns 0 on exit
|
||||
*/
|
||||
int main() {
|
||||
test(); // run self-test implementations
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief
|
||||
* [Prefix Sum
|
||||
* Array](https://en.wikipedia.org/wiki/Prefix_sum) data structure
|
||||
* implementation.
|
||||
*
|
||||
* @details
|
||||
* Prefix Sum Array is a data structure, that allows answering sum in some range
|
||||
* queries. It can answer most sum range queries in O(1), with a build time
|
||||
* complexity of O(N). But it hasn't an update querie.
|
||||
*
|
||||
* * Running Time Complexity \n
|
||||
* * Build : O(N) \n
|
||||
* * Range Query : O(1) \n
|
||||
* @author [Paulo Vitor Lima Borges](https://github.com/PauloVLB)
|
||||
*/
|
||||
|
||||
#include <cassert> /// for assert
|
||||
#include <iostream> /// for IO operations
|
||||
#include <vector> /// for std::vector
|
||||
|
||||
/**
|
||||
* @namespace range_queries
|
||||
* @brief Range Queries algorithms
|
||||
*/
|
||||
namespace range_queries {
|
||||
/**
|
||||
* @namespace prefix_sum_array
|
||||
* @brief Range sum queries using prefix-sum-array
|
||||
*/
|
||||
namespace prefix_sum_array {
|
||||
|
||||
std::vector<int64_t> PSA{};
|
||||
|
||||
/**
|
||||
* @brief function that builds the PSA
|
||||
* @param original_array original array of values
|
||||
* @returns void
|
||||
*/
|
||||
void build(const std::vector<int64_t>& original_array) {
|
||||
PSA.clear();
|
||||
PSA.push_back(0);
|
||||
for (std::size_t i = 1; i < original_array.size(); ++i) {
|
||||
PSA.push_back(PSA.back() + original_array[i]);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @brief query function
|
||||
* @param beg begin of the interval to sum
|
||||
* @param end end of the interval to sum
|
||||
* @returns sum of the range [beg, end]
|
||||
*/
|
||||
int64_t query(int64_t beg, int64_t end) { return PSA[end] - PSA[beg - 1]; }
|
||||
|
||||
} // namespace prefix_sum_array
|
||||
} // namespace range_queries
|
||||
|
||||
/**
|
||||
* @brief Self-test implementations
|
||||
* @returns void
|
||||
*/
|
||||
static void test() {
|
||||
std::vector<int64_t> values{0, 123, 0, 2, -2, 5,
|
||||
24, 0, 23, -1, -1}; // original array
|
||||
|
||||
range_queries::prefix_sum_array::build(values);
|
||||
// queries are of the type: sum of the range [a, b] = psa[b] - psa[a-1]
|
||||
|
||||
assert(range_queries::prefix_sum_array::query(1, 10) ==
|
||||
173); // sum of the entire array
|
||||
assert(range_queries::prefix_sum_array::query(4, 6) ==
|
||||
27); // the sum of the interval [4, 6]
|
||||
assert(range_queries::prefix_sum_array::query(5, 9) ==
|
||||
51); // the sum of the interval [5, 9]
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Main function
|
||||
* @returns 0 on exit
|
||||
*/
|
||||
int main() {
|
||||
test(); // run self-test implementations
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief Implementation of [Segment Tree]
|
||||
* (https://en.wikipedia.org/wiki/Segment_tree) data structure
|
||||
*
|
||||
* @details
|
||||
* A segment tree, also known as a statistic tree, is a tree data structure used
|
||||
* for storing information about intervals, or segments. Its classical version
|
||||
* allows querying which of the stored segments contain a given point, but our
|
||||
* modification allows us to perform (query) any binary operation on any range
|
||||
* in the array in O(logN) time. Here, we have used addition (+).
|
||||
* For range updates, we have used lazy propagation.
|
||||
*
|
||||
* * Space Complexity : O(NlogN) \n
|
||||
* * Build Time Complexity : O(NlogN) \n
|
||||
* * Query Time Complexity : O(logN) \n
|
||||
*
|
||||
* @author [Madhav Gaba](https://github.com/madhavgaba)
|
||||
* @author [Soham Roy](https://github.com/sohamroy19)
|
||||
*/
|
||||
|
||||
#include <cassert> /// for assert
|
||||
#include <cmath> /// for log2
|
||||
#include <cstdint> /// for std::uint64_t
|
||||
#include <iostream> /// for IO operations
|
||||
#include <vector> /// for std::vector
|
||||
|
||||
/**
|
||||
* @brief Constructs the initial segment tree
|
||||
*
|
||||
* @param arr input to construct the tree out of
|
||||
* @param segtree the segment tree
|
||||
* @param low inclusive lowest index of arr to begin at
|
||||
* @param high inclusive highest index of arr to end at
|
||||
* @param pos index of segtree to fill (eg. root node)
|
||||
* @returns void
|
||||
*/
|
||||
void ConsTree(const std::vector<int64_t> &arr, std::vector<int64_t> *segtree,
|
||||
uint64_t low, uint64_t high, uint64_t pos) {
|
||||
if (low == high) {
|
||||
(*segtree)[pos] = arr[low];
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t mid = (low + high) / 2;
|
||||
ConsTree(arr, segtree, low, mid, 2 * pos + 1);
|
||||
ConsTree(arr, segtree, mid + 1, high, 2 * pos + 2);
|
||||
(*segtree)[pos] = (*segtree)[2 * pos + 1] + (*segtree)[2 * pos + 2];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the sum of all elements in a range
|
||||
*
|
||||
* @param segtree the segment tree
|
||||
* @param lazy for lazy propagation
|
||||
* @param qlow lower index of the required query
|
||||
* @param qhigh higher index of the required query
|
||||
* @param low lower index of query for this function call
|
||||
* @param high higher index of query for this function call
|
||||
* @param pos index of segtree to consider (eg. root node)
|
||||
* @return result of the range query for this function call
|
||||
*/
|
||||
int64_t query(std::vector<int64_t> *segtree, std::vector<int64_t> *lazy,
|
||||
uint64_t qlow, uint64_t qhigh, uint64_t low, uint64_t high,
|
||||
uint64_t pos) {
|
||||
if (low > high || qlow > high || low > qhigh) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((*lazy)[pos] != 0) {
|
||||
(*segtree)[pos] += (*lazy)[pos] * (high - low + 1);
|
||||
|
||||
if (low != high) {
|
||||
(*lazy)[2 * pos + 1] += (*lazy)[pos];
|
||||
(*lazy)[2 * pos + 2] += (*lazy)[pos];
|
||||
}
|
||||
(*lazy)[pos] = 0;
|
||||
}
|
||||
|
||||
if (qlow <= low && qhigh >= high) {
|
||||
return (*segtree)[pos];
|
||||
}
|
||||
|
||||
uint64_t mid = (low + high) / 2;
|
||||
|
||||
return query(segtree, lazy, qlow, qhigh, low, mid, 2 * pos + 1) +
|
||||
query(segtree, lazy, qlow, qhigh, mid + 1, high, 2 * pos + 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates a range of the segment tree
|
||||
*
|
||||
* @param segtree the segment tree
|
||||
* @param lazy for lazy propagation
|
||||
* @param start lower index of the required query
|
||||
* @param end higher index of the required query
|
||||
* @param delta integer to add to each element of the range
|
||||
* @param low lower index of query for this function call
|
||||
* @param high higher index of query for this function call
|
||||
* @param pos index of segtree to consider (eg. root node)
|
||||
* @returns void
|
||||
*/
|
||||
void update(std::vector<int64_t> *segtree, std::vector<int64_t> *lazy,
|
||||
int64_t start, int64_t end, int64_t delta, uint64_t low,
|
||||
uint64_t high, uint64_t pos) {
|
||||
if (low > high) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((*lazy)[pos] != 0) {
|
||||
(*segtree)[pos] += (*lazy)[pos] * (high - low + 1);
|
||||
|
||||
if (low != high) {
|
||||
(*lazy)[2 * pos + 1] += (*lazy)[pos];
|
||||
(*lazy)[2 * pos + 2] += (*lazy)[pos];
|
||||
}
|
||||
(*lazy)[pos] = 0;
|
||||
}
|
||||
|
||||
if (start > high || end < low) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (start <= low && end >= high) {
|
||||
(*segtree)[pos] += delta * (high - low + 1);
|
||||
|
||||
if (low != high) {
|
||||
(*lazy)[2 * pos + 1] += delta;
|
||||
(*lazy)[2 * pos + 2] += delta;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t mid = (low + high) / 2;
|
||||
|
||||
update(segtree, lazy, start, end, delta, low, mid, 2 * pos + 1);
|
||||
update(segtree, lazy, start, end, delta, mid + 1, high, 2 * pos + 2);
|
||||
(*segtree)[pos] = (*segtree)[2 * pos + 1] + (*segtree)[2 * pos + 2];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Self-test implementation
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
static void test() {
|
||||
auto max = static_cast<int64_t>(2 * pow(2, ceil(log2(7))) - 1);
|
||||
assert(max == 15);
|
||||
|
||||
std::vector<int64_t> arr{1, 2, 3, 4, 5, 6, 7}, lazy(max), segtree(max);
|
||||
ConsTree(arr, &segtree, 0, 7 - 1, 0);
|
||||
|
||||
assert(query(&segtree, &lazy, 1, 5, 0, 7 - 1, 0) == 2 + 3 + 4 + 5 + 6);
|
||||
|
||||
update(&segtree, &lazy, 2, 4, 1, 0, 7 - 1, 0);
|
||||
assert(query(&segtree, &lazy, 1, 5, 0, 7 - 1, 0) == 2 + 4 + 5 + 6 + 6);
|
||||
|
||||
update(&segtree, &lazy, 0, 6, -2, 0, 7 - 1, 0);
|
||||
assert(query(&segtree, &lazy, 0, 4, 0, 7 - 1, 0) == -1 + 0 + 2 + 3 + 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Main function
|
||||
*
|
||||
* @return 0 on exit
|
||||
*/
|
||||
int main() {
|
||||
test(); // run self-test implementations
|
||||
|
||||
std::cout << "Enter number of elements: ";
|
||||
|
||||
uint64_t n = 0;
|
||||
std::cin >> n;
|
||||
|
||||
auto max = static_cast<uint64_t>(2 * pow(2, ceil(log2(n))) - 1);
|
||||
std::vector<int64_t> arr(n), lazy(max), segtree(max);
|
||||
|
||||
int choice = 0;
|
||||
std::cout << "\nDo you wish to enter each number?:\n"
|
||||
"1: Yes\n"
|
||||
"0: No (default initialize them to 0)\n";
|
||||
|
||||
std::cin >> choice;
|
||||
if (choice == 1) {
|
||||
std::cout << "Enter " << n << " numbers:\n";
|
||||
for (int i = 1; i <= n; i++) {
|
||||
std::cout << i << ": ";
|
||||
std::cin >> arr[i];
|
||||
}
|
||||
}
|
||||
|
||||
ConsTree(arr, &segtree, 0, n - 1, 0);
|
||||
|
||||
do {
|
||||
std::cout << "\nMake your choice:\n"
|
||||
"1: Range update (input)\n"
|
||||
"2: Range query (output)\n"
|
||||
"0: Exit\n";
|
||||
std::cin >> choice;
|
||||
|
||||
if (choice == 1) {
|
||||
std::cout << "Enter 1-indexed lower bound, upper bound & value:\n";
|
||||
|
||||
uint64_t p = 1, q = 1, v = 0;
|
||||
std::cin >> p >> q >> v;
|
||||
update(&segtree, &lazy, p - 1, q - 1, v, 0, n - 1, 0);
|
||||
} else if (choice == 2) {
|
||||
std::cout << "Enter 1-indexed lower bound & upper bound:\n";
|
||||
|
||||
uint64_t p = 1, q = 1;
|
||||
std::cin >> p >> q;
|
||||
std::cout << query(&segtree, &lazy, p - 1, q - 1, 0, n - 1, 0);
|
||||
std::cout << "\n";
|
||||
}
|
||||
} while (choice > 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* @file sparse_table.cpp
|
||||
* @brief Implementation of [Sparse
|
||||
* Table](https://en.wikipedia.org/wiki/Range_minimum_query) data structure
|
||||
*
|
||||
* @details
|
||||
* Sparse Table is a data structure, that allows answering range queries.
|
||||
* It can answer most range queries in O(logn), but its true power is answering
|
||||
* range minimum queries or equivalent range maximum queries). For those queries
|
||||
* it can compute the answer in O(1) time.
|
||||
*
|
||||
* * Running Time Complexity \n
|
||||
* * Build : O(NlogN) \n
|
||||
* * Range Query : O(1) \n
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* @namespace range_queries
|
||||
* @brief Range Queries algorithms
|
||||
*/
|
||||
namespace range_queries {
|
||||
/**
|
||||
* @namespace sparse_table
|
||||
* @brief Range queries using sparse-tables
|
||||
*/
|
||||
namespace sparse_table {
|
||||
/**
|
||||
* This function precomputes intial log table for further use.
|
||||
* @param n value of the size of the input array
|
||||
* @return corresponding vector of the log table
|
||||
*/
|
||||
template <typename T>
|
||||
std::vector<T> computeLogs(const std::vector<T>& A) {
|
||||
int n = A.size();
|
||||
std::vector<T> logs(n);
|
||||
logs[1] = 0;
|
||||
for (int i = 2; i < n; i++) {
|
||||
logs[i] = logs[i / 2] + 1;
|
||||
}
|
||||
return logs;
|
||||
}
|
||||
|
||||
/**
|
||||
* This functions builds the primary data structure sparse table
|
||||
* @param n value of the size of the input array
|
||||
* @param A array of the input integers
|
||||
* @param logs array of the log table
|
||||
* @return created sparse table data structure
|
||||
*/
|
||||
template <typename T>
|
||||
std::vector<std::vector<T> > buildTable(const std::vector<T>& A,
|
||||
const std::vector<T>& logs) {
|
||||
int n = A.size();
|
||||
std::vector<std::vector<T> > table(20, std::vector<T>(n + 5, 0));
|
||||
int curLen = 0;
|
||||
for (int i = 0; i <= logs[n]; i++) {
|
||||
curLen = 1 << i;
|
||||
for (int j = 0; j + curLen < n; j++) {
|
||||
if (curLen == 1) {
|
||||
table[i][j] = A[j];
|
||||
} else {
|
||||
table[i][j] =
|
||||
std::min(table[i - 1][j], table[i - 1][j + curLen / 2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is the query function to get the range minimum value
|
||||
* @param beg beginning index of the query range
|
||||
* @param end ending index of the query range
|
||||
* @param logs array of the log table
|
||||
* @param table sparse table data structure for the input array
|
||||
* @return minimum value for the [beg, end] range for the input array
|
||||
*/
|
||||
template <typename T>
|
||||
int getMinimum(int beg, int end, const std::vector<T>& logs,
|
||||
const std::vector<std::vector<T> >& table) {
|
||||
int p = logs[end - beg + 1];
|
||||
int pLen = 1 << p;
|
||||
return std::min(table[p][beg], table[p][end - pLen + 1]);
|
||||
}
|
||||
} // namespace sparse_table
|
||||
} // namespace range_queries
|
||||
|
||||
/**
|
||||
* Main function
|
||||
*/
|
||||
int main() {
|
||||
std::vector<int> A{1, 2, 0, 3, 9};
|
||||
std::vector<int> logs = range_queries::sparse_table::computeLogs(A);
|
||||
std::vector<std::vector<int> > table =
|
||||
range_queries::sparse_table::buildTable(A, logs);
|
||||
assert(range_queries::sparse_table::getMinimum(0, 0, logs, table) == 1);
|
||||
assert(range_queries::sparse_table::getMinimum(0, 4, logs, table) == 0);
|
||||
assert(range_queries::sparse_table::getMinimum(2, 4, logs, table) == 0);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user