From 603e7eadb3d92d98095d8688b0b37cbff42bf38d Mon Sep 17 00:00:00 2001 From: Ivan Li <104738598+A-stick-bug@users.noreply.github.com> Date: Thu, 7 Dec 2023 18:07:46 -0500 Subject: [PATCH 1/2] added range update code --- src/data_structures/sqrt_decomposition.md | 49 +++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/data_structures/sqrt_decomposition.md b/src/data_structures/sqrt_decomposition.md index 7362733f7..88f63b8e6 100644 --- a/src/data_structures/sqrt_decomposition.md +++ b/src/data_structures/sqrt_decomposition.md @@ -110,6 +110,55 @@ For example, let's say we can do two types of operations on an array: add a give Finally, those two classes of problems can be combined if the task requires doing **both** element updates on an interval and queries on an interval. Both operations can be done with $O(\sqrt{n})$ complexity. This will require two block arrays $b$ and $c$: one to keep track of element updates and another to keep track of answers to the query. +```py +class SqrtDecomp: + def __init__(self, nums): + self.nums = nums + self.width = int(len(nums) ** 0.5) + 1 + self.bn = (len(nums) // self.width) + 1 # number of blocks + self.blocks = [0] * self.bn # precomputed sums + self.lazy = [0] * self.bn # an additional lazy[block] is added when querying individual elements in a block + for i in range(len(nums)): + self.blocks[i // self.width] += nums[i] # add to corresponding block + + def update(self, i, j, diff): + first = (i // self.width) + 1 # first fully covered block + last = (j // self.width) - 1 # last fully covered block + if first > last: # doesn't cover any blocks + for v in range(i, j + 1): + self.nums[v] += diff + self.blocks[v // self.width] += diff + return + + for b in range(first, last + 1): # blocks in the middle + self.lazy[b] += diff + self.blocks[b] += diff * self.width + for v in range(i, first * self.width): # individual cells + self.nums[v] += diff + self.blocks[first - 1] += diff + for v in range((last + 1) * self.width, j + 1): + self.nums[v] += diff + self.blocks[last + 1] += diff + + def query(self, i, j): + first = (i // self.width) + 1 # first fully covered block + last = (j // self.width) - 1 # last fully covered block + res = 0 + + if first > last: # doesn't cover any blocks + for v in range(i, j + 1): + res += self.nums[v] + self.lazy[v // self.width] + return res + + for b in range(first, last + 1): # add value from blocks + res += self.blocks[b] + for v in range(i, first * self.width): # add value from individual cells + res += self.nums[v] + self.lazy[first - 1] + for v in range((last + 1) * self.width, j + 1): + res += self.nums[v] + self.lazy[last + 1] + return res +``` + There exist other problems which can be solved using sqrt decomposition, for example, a problem about maintaining a set of numbers which would allow adding/deleting numbers, checking whether a number belongs to the set and finding $k$-th largest number. To solve it one has to store numbers in increasing order, split into several blocks with $\sqrt{n}$ numbers in each. Every time a number is added/deleted, the blocks have to be rebalanced by moving numbers between beginnings and ends of adjacent blocks. ## Mo's algorithm From 709935173c0d5cfd91706dda7378c1171dfa4e49 Mon Sep 17 00:00:00 2001 From: Ivan Li <104738598+A-stick-bug@users.noreply.github.com> Date: Sat, 6 Jan 2024 13:39:36 -0500 Subject: [PATCH 2/2] wrote in C++ and made it consistent --- src/data_structures/sqrt_decomposition.md | 129 ++++++++++++++-------- 1 file changed, 80 insertions(+), 49 deletions(-) diff --git a/src/data_structures/sqrt_decomposition.md b/src/data_structures/sqrt_decomposition.md index 88f63b8e6..e1bfd9d48 100644 --- a/src/data_structures/sqrt_decomposition.md +++ b/src/data_structures/sqrt_decomposition.md @@ -108,55 +108,86 @@ Another class of problems appears when we need to **update array elements on int For example, let's say we can do two types of operations on an array: add a given value $\delta$ to all array elements on interval $[l, r]$ or query the value of element $a[i]$. Let's store the value which has to be added to all elements of block $k$ in $b[k]$ (initially all $b[k] = 0$). During each "add" operation we need to add $\delta$ to $b[k]$ for all blocks which belong to interval $[l, r]$ and to add $\delta$ to $a[i]$ for all elements which belong to the "tails" of the interval. The answer a query $i$ is simply $a[i] + b[i/s]$. This way "add" operation has $O(\sqrt{n})$ complexity, and answering a query has $O(1)$ complexity. -Finally, those two classes of problems can be combined if the task requires doing **both** element updates on an interval and queries on an interval. Both operations can be done with $O(\sqrt{n})$ complexity. This will require two block arrays $b$ and $c$: one to keep track of element updates and another to keep track of answers to the query. - -```py -class SqrtDecomp: - def __init__(self, nums): - self.nums = nums - self.width = int(len(nums) ** 0.5) + 1 - self.bn = (len(nums) // self.width) + 1 # number of blocks - self.blocks = [0] * self.bn # precomputed sums - self.lazy = [0] * self.bn # an additional lazy[block] is added when querying individual elements in a block - for i in range(len(nums)): - self.blocks[i // self.width] += nums[i] # add to corresponding block - - def update(self, i, j, diff): - first = (i // self.width) + 1 # first fully covered block - last = (j // self.width) - 1 # last fully covered block - if first > last: # doesn't cover any blocks - for v in range(i, j + 1): - self.nums[v] += diff - self.blocks[v // self.width] += diff - return - - for b in range(first, last + 1): # blocks in the middle - self.lazy[b] += diff - self.blocks[b] += diff * self.width - for v in range(i, first * self.width): # individual cells - self.nums[v] += diff - self.blocks[first - 1] += diff - for v in range((last + 1) * self.width, j + 1): - self.nums[v] += diff - self.blocks[last + 1] += diff - - def query(self, i, j): - first = (i // self.width) + 1 # first fully covered block - last = (j // self.width) - 1 # last fully covered block - res = 0 - - if first > last: # doesn't cover any blocks - for v in range(i, j + 1): - res += self.nums[v] + self.lazy[v // self.width] - return res - - for b in range(first, last + 1): # add value from blocks - res += self.blocks[b] - for v in range(i, first * self.width): # add value from individual cells - res += self.nums[v] + self.lazy[first - 1] - for v in range((last + 1) * self.width, j + 1): - res += self.nums[v] + self.lazy[last + 1] - return res +Finally, those two classes of problems can be combined if the task requires doing **both** element updates on an interval and queries on an interval. Both operations can be done with $O(\sqrt{n})$ complexity. This will require two block arrays $b$ and $c$: one to keep track of element updates and another to keep track of answers to the query. An example of how to do this is shown below. + +```cpp +class SqrtDecomp { + vector a, b, c; + int len, block_count; + +public: + SqrtDecomp(vector& input) { + len = sqrt(input.size()); + this->a = input; + this->a.resize(input.size() + len, 0); + block_count = (input.size() / len) + 1; + b.resize(block_count, 0); + c.resize(block_count, 0); // an additional c[block] is added when querying elements in block + for (int i = 0; i < block_count; ++i) { // compute block sums + b[i] = accumulate(a.begin() + i * len, a.begin() + (i + 1) * len, 0); + } + } + void update(int l, int r, int diff) { + int c_l = (l / len) + 1; + int c_r = (r / len) - 1; + if (c_l > c_r) { // doesn't cover any blocks + for (int i = l; i <= r; ++i) { + a[i] += diff; + b[i / len] += diff; + } + return; + } + + for (int i = c_l; i <= c_r; ++i) { // update blocks + c[i] += diff; + b[i] += diff * len; + } + for (int i = l; i < c_l * len; ++i) { // update individual cells + a[i] += diff; + b[c_l - 1] += diff; + } + for (int i = (c_r + 1) * len; i <= r; ++i) { + a[i] += diff; + b[c_r + 1] += diff; + } + } + + int query(int l, int r) { + int c_l = (l / len) + 1; + int c_r = (r / len) - 1; + int sum = 0; + + if (c_l > c_r) { // doesn't cover any blocks + for (int i = l; i <= r; ++i) { + sum += a[i] + c[i / len]; + } + return sum; + } + + for (int i = c_l; i <= c_r; ++i) { // add value from blocks + sum += b[i]; + } + for (int i = l; i < c_l * len; ++i) { // add value from individual cells + sum += a[i] + c[c_l - 1]; + } + for (int i = (c_r + 1) * len; i <= r; ++i) { + sum += a[i] + c[c_r + 1]; + } + return sum; + } +}; +``` + +We can now perform updates and queries on an interval. +```cpp +vector a(N); +// create a from input + +SqrtDecomp sq(a); +int l, r, x; +// read input data for the next query +sq.update(l, r, x); // add x to every element on the interval [l, r] +cout << sq.query(l, r) << "\n"; // query the sum of elements in the interval [l, r] ``` There exist other problems which can be solved using sqrt decomposition, for example, a problem about maintaining a set of numbers which would allow adding/deleting numbers, checking whether a number belongs to the set and finding $k$-th largest number. To solve it one has to store numbers in increasing order, split into several blocks with $\sqrt{n}$ numbers in each. Every time a number is added/deleted, the blocks have to be rebalanced by moving numbers between beginnings and ends of adjacent blocks.