This commit is contained in:
MSedra 2021-02-01 00:44:29 +02:00
parent 020c6141c3
commit 4b6566d2f9

View File

@ -1,6 +1,6 @@
/** /**
* @file * @file
* @brief [Persistent segment tree with range updates (lazy propagation)](https://en.wikipedia.org/wiki/Persistent_data_structure - https://www.geeksforgeeks.org/lazy-propagation-in-segment-tree/) * @brief [Persistent segment tree with range updates (lazy propagation)](https://en.wikipedia.org/wiki/Persistent_data_structure)
* *
* @details * @details
* A normal segment tree facilitates making point updates and range queries in logarithmic time. * A normal segment tree facilitates making point updates and range queries in logarithmic time.
@ -15,193 +15,205 @@
* *
* @author [Magdy Sedra](https://github.com/MSedra) * @author [Magdy Sedra](https://github.com/MSedra)
*/ */
#include<iostream> #include<iostream> /// for IO operations
#include<vector> #include<vector> /// for std::vector
#include <memory> /// to manage dynamic memory #include <memory> /// to manage dynamic memory
/// Range query here is range sum ,but the code can be modified to make different queries like range max or min /**
class perSegTree { * @namespace range_queries
private: * @brief Range queries algorithms
class Node { */
public: namespace range_queries {
std::shared_ptr<Node> left; /// pointer to the left node
std::shared_ptr<Node> right; /// pointer to the right node
int64_t val, prop; /// 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
Node() { /**
val = prop = 0; * @brief Range query here is range sum, but the code can be modified to make different queries like range max or min.
left = right = nullptr; */
} class perSegTree {
}; private:
class Node {
public:
std::shared_ptr<Node> left; /// pointer to the left node
std::shared_ptr<Node> right; /// pointer to the right node
int64_t val, prop; /// 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
int n; /// number of elements/leaf nodes in the segment tree Node() {
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 val = prop = 0;
std::vector<int> vec; /// values of the leaf nodes that the segment tree will be constructed with left = right = nullptr;
}
};
/** int n; /// number of elements/leaf nodes in the segment tree
* @brief Creating a new node with the same values of curr node 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
* @param curr node that would be copied std::vector<int> vec; /// values of the leaf nodes that the segment tree will be constructed with
* @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 * @brief Creating a new node with the same values of curr node
* @param i the left index of the range that the passed node holds its sum * @param curr node that would be copied
* @param j the right index of the range that the passed node holds its sum * @return the new node
* @param curr pointer to the node to be propagated */
* @returns void std::shared_ptr<Node> newKid(std::shared_ptr<Node> const &curr) {
*/ auto newNode = std::make_shared<Node>(Node());
void lazy(int i, int j, std::shared_ptr<Node> const &curr) { newNode->left = curr->left;
if (!curr->prop) { newNode->right = curr->right;
return; newNode->prop = curr->prop;
} newNode->val = curr->val;
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(int i, int j) {
auto newNode = std::make_shared<Node>(Node());
if (i == j) {
newNode->val = vec[i];
} else {
int 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
* @return
*/
std::shared_ptr<Node> update(int i, int j, int l, int r, int 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; return newNode;
} }
if (i > r || j < l) {
return curr; /**
* @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
* @return void
*/
void lazy(const int &i, const int &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;
} }
auto newNode = std::make_shared<Node>(Node());
int 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 * @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 passed node holds its sum * @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 passed node holds its sum * @param j the right index of the range that the created node holds its sum
* @param l the left index of the range whose sum should be returned as a result * @return pointer to the newly created node
* @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 std::shared_ptr<Node> construct(const int &i, const int &j) {
* @return sum of elements whose index x satisfies l<=x<=r auto newNode = std::make_shared<Node>(Node());
*/ if (i == j) {
int64_t query(int i, int j, int l, int r, std::shared_ptr<Node> const &curr) { newNode->val = vec[i];
lazy(i, j, curr); } else {
if (j < l || r < i) { int mid = i + (j - i) / 2;
return 0; 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;
} }
if (i >= l && j <= r) {
return curr->val; /**
* @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
* @return pointer to the current newly created node
*/
std::shared_ptr<Node> update(const int &i, const int &j, const int &l, const int &r, const int &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());
int 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;
} }
int 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:
perSegTree() {
n = 0;
}
/** /**
* @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 * @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 vec vector whose values will be used to build the segment tree * @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
void construct(const std::vector<int> &vec) // the segment tree will be built from the values in "vec", "vec" is 0 indexed * @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
if (vec.empty()) { * @param curr pointer to the current node, which has value = the sum of elements whose index x satisfies i<=x<=j
return; * @return sum of elements whose index x satisfies l<=x<=r
*/
int64_t query(const int &i, const int &j, const int &l, const int &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;
}
int mid = i + (j - i) / 2;
return query(i, mid, l, r, curr->left) + query(mid + 1, j, l, r, curr->right);
} }
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 indices of the range as well as the value to be added. * public methods that can be used directly from outside the class. They call the private functions that do all the work
* @param l the left index of the range to be updated */
* @param r the right index of the range to be updated public:
* @param value the value to be added to every element whose index x satisfies l<=x<=r perSegTree() {
*/ n = 0;
void update(int l, int r, int 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 * @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 l the left index of the range whose sum should be returned as a result * @param vec vector whose values will be used to build the segment tree
* @param r the right index of the range whose sum should be returned as a result * @return void
* @param version the version to query on. If equals to 0, the original segment tree will be queried */
* @return sum of elements whose index x satisfies l<=x<=r void construct(const std::vector<int> &vec) // the segment tree will be built from the values in "vec", "vec" is 0 indexed
*/ {
int64_t query(int l, int r, int version) // querying the range from "l" to "r" in a segment tree after "version" updates, "l" and "r" are 0 indexed if (vec.empty()) {
{ return;
return query(0, n - 1, l, r, ptrs[version]); }
} n = vec.size();
this->vec = vec;
auto root = construct(0, n - 1);
ptrs.push_back(root);
}
/** /**
* @brief Getting the number of versions after updates so far which is equal to the size of the pointers vector * @brief Doing range update by passing the left and right indices of the range as well as the value to be added.
* @return the number of versions * @param l the left index of the range to be updated
*/ * @param r the right index of the range to be updated
int 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 * @param value the value to be added to every element whose index x satisfies l<=x<=r
{ * @return void
return ptrs.size(); */
} void update(const int &l, const int &r, const int &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
* @return sum of elements whose index x satisfies l<=x<=r
*/
int64_t query(const int &l, const int &r, const int &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
* @return the number of versions
*/
int 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
void test() { void test() {
std::vector<int> arr = {-5, 2, 3, 11, -2, 7, 0, 1}; std::vector<int> arr = {-5, 2, 3, 11, -2, 7, 0, 1};
perSegTree tree; range_queries::perSegTree tree;
std::cout << "Elements before any updates are {"; std::cout << "Elements before any updates are {";
for (int i = 0; i < arr.size(); ++i) { for (int i = 0; i < arr.size(); ++i) {
std::cout << arr[i]; std::cout << arr[i];
@ -238,6 +250,9 @@ void test() {
} }
int main() { int main() {
test(); test(); // run self-test implementations
return 0; return 0;
} }