|
2 | 2 | id: cses-2416
|
3 | 3 | source: CSES
|
4 | 4 | title: Increasing Array Queries
|
5 |
| -author: Andi Qu |
| 5 | +author: Andi Qu, Kevin Sheng |
6 | 6 | ---
|
7 | 7 |
|
8 |
| -**Time Complexity:** $\mathcal O((N + Q) \log N)$. |
| 8 | +<Spoiler title="Hint 1"> |
| 9 | + |
| 10 | +Can you first come up with an algorithm for a single query? |
| 11 | +It doesn't have to be crazy efficient; $\mathcal{O}(N)$ time is fine. |
| 12 | + |
| 13 | +</Spoiler> |
| 14 | + |
| 15 | +<Spoiler title="Answer to Hint 1"> |
| 16 | + |
| 17 | +Let's iterate through the array in order and keep track of the largest element $m$ we've seen so far. |
| 18 | +For each element $x$, we first update $m$. |
| 19 | +Then, if the current one is smaller than $m$, we add $m-x$ to the total. |
| 20 | + |
| 21 | +</Spoiler> |
| 22 | + |
| 23 | +<Spoiler title="Hint 2"> |
| 24 | + |
| 25 | +Since we can't process each query like this, let's try to come up with the concept |
| 26 | +of a "contribution" of a certain element. |
| 27 | + |
| 28 | +Take the array $[10,4,11,3]$, for example. |
| 29 | +Try to think of how $10$ and $11$ in particular contribute to the answer. |
9 | 30 |
|
10 |
| -In this problem, we can answer the queries offline (i.e. read in the queries and |
11 |
| -process them in a different order). More specifically, we process queries in |
12 |
| -order of their left endpoints. |
| 31 | +</Spoiler> |
13 | 32 |
|
14 |
| -First, let's think about how we'd answer a single query $(l, r)$. Consider the |
15 |
| -following algorithm: |
| 33 | +<Spoiler title="Solution"> |
16 | 34 |
|
17 |
| -- Let $\texttt{mx}_0 = 0$ and $j = 0$. |
18 |
| -- For each $i$ in $[l, r]$: |
19 |
| - - If $x_i > \texttt{mx}_j$, then set $\texttt{mx}_{j + 1} = x_i$ and increment |
20 |
| - $j$. |
21 |
| - - Otherwise, add $\texttt{mx}_j - x_i$ to the answer. |
| 35 | +## Explanation |
22 | 36 |
|
23 |
| -Notice how each $\texttt{mx}_j$ (except the last) contributes a fixed amount to |
24 |
| -the answer, regardless of $r$. If $\texttt{pref}$ is the prefix sum array of $x$ |
25 |
| -and $\texttt{idx}_j$ is the index of $\texttt{mx}_j$ in $x$, then this amount is |
| 37 | +### Setup |
| 38 | + |
| 39 | +Let's follow the line of thought from Hint 2. |
| 40 | +Say we have an element of value $x$ at index $i$, and the next element strictly greater than $x$ is at index $j$. |
| 41 | +If there is no next greater element, let $j$ be one greater than the index of the final element. |
| 42 | + |
| 43 | +The contribution from $x$ to a query would be as follows: |
26 | 44 |
|
27 | 45 | $$
|
28 |
| -(\texttt{idx}_{j + 1} - 1 - \texttt{idx}_j) \cdot \texttt{mx}_j - (\texttt{pref}_{\texttt{idx}_{j + 1} - 1} - \texttt{pref}_{\texttt{idx}_j}) |
| 46 | +x \cdot (j-i) - \sum_{k=i}^{j-1} \texttt{arr}[k] |
29 | 47 | $$
|
30 | 48 |
|
31 |
| -This means that for a fixed $l$, we can compute each $\texttt{mx}_j$ and their |
32 |
| -contributions, and then use `std::upper_bound` and a BIT to answer queries with |
33 |
| -variable $r$! |
| 49 | +The second term is the sum of all elements in the interval $[i,j)$. |
34 | 50 |
|
35 |
| -If we want to change $l$, notice how we can update $\texttt{mx}$ efficiently by |
36 |
| -using a monotone stack. Since each $\texttt{mx}_j$'s contribution depends on |
37 |
| -$x_i$ **after** it, the contributions of the old $\texttt{mx}_j$ don't change. |
| 51 | +For example, in that array in Hint 2, $10$ contributes $10 \cdot (2-0)-(10+4)=6$, while $11$ contributes $11 \cdot (4-2)-(11+3)=8$ to the sum. |
38 | 52 |
|
39 |
| -Again, we use `std::upper_bound` and a BIT to answer queries. |
| 53 | +Notice that this doesn't apply when an element's $j$ goes past the end of the query. |
| 54 | +If we only considered the first three elements of Hint 2's array, $11$ would not contribute anything. |
40 | 55 |
|
41 |
| -<LanguageSection> |
| 56 | +### Execution |
42 | 57 |
|
43 |
| -<CPPSection> |
| 58 | +How do we utilize this? |
44 | 59 |
|
45 |
| -```cpp |
46 |
| -#include <bits/stdc++.h> |
47 |
| -typedef long long ll; |
48 |
| -using namespace std; |
| 60 | +First, we have to iterate through the array in reverse. |
49 | 61 |
|
50 |
| -const ll INF = 1e18; |
| 62 | +Let's also keep track of a stack of the values and indices of all the maximums one would get when iterating from an index $i$ to the end of the array. |
51 | 63 |
|
52 |
| -int n, q; |
53 |
| -ll x[200002], pref[200002], ans[200002]; |
54 |
| -ll contrib[200002], bit[200002]; |
55 |
| -vector<pair<int, int>> bckt[200001]; |
| 64 | +Take the array from Hint 2 again. If we were at index $1$, our stack would be $[(11, 2), (4, 1)]$, with the end of the array being the top of the stack. |
56 | 65 |
|
57 |
| -void update(int pos, ll val) { |
58 |
| - for (; pos <= n; pos += pos & -pos) bit[pos] += val; |
59 |
| -} |
| 66 | +Each element in this stack would contribute the amount given by our formula above to a query's answers, *given that it's completely contained within said query*. |
60 | 67 |
|
61 |
| -ll query(int a, int b) { |
62 |
| - ll ans = 0; |
63 |
| - for (; b; b -= b & -b) ans += bit[b]; |
64 |
| - for (a--; a; a -= a & -a) ans -= bit[a]; |
65 |
| - return ans; |
66 |
| -} |
| 68 | +Since the indices in the stack are always decreasing, we can use a binary search to find where the query lies within the stack. |
| 69 | +For the tail element that might not be completely contained within the query, we can compute the contribution manually and add it on at the end. |
| 70 | + |
| 71 | +To efficiently update contributions relative to the stack, we'll use a [BIT](/gold/PURS#solution---dynamic-range-sum-queries-with-a-bit). |
| 72 | +We also need a prefix sum array to efficiently calculate the second term of that contribution formula back there. |
| 73 | + |
| 74 | +## Implementation |
| 75 | + |
| 76 | +**Time Complexity:** $\mathcal O((N + Q) \log N)$. |
| 77 | + |
| 78 | +<LanguageSection> |
| 79 | +<CPPSection> |
| 80 | + |
| 81 | +```cpp |
| 82 | +#include <algorithm> |
| 83 | +#include <iostream> |
| 84 | +#include <vector> |
| 85 | + |
| 86 | +using std::cout; |
| 87 | +using std::endl; |
| 88 | +using std::pair; |
| 89 | +using std::vector; |
| 90 | +using ll = long long; |
| 91 | + |
| 92 | +// BeginCodeSnip{BIT (from the module)} |
| 93 | +template <class T> class BIT { |
| 94 | + private: |
| 95 | + int size; |
| 96 | + vector<T> bit; |
| 97 | + vector<T> arr; |
| 98 | + |
| 99 | + public: |
| 100 | + BIT(int size) : size(size), bit(size + 1), arr(size) {} |
| 101 | + |
| 102 | + void set(int ind, T val) { add(ind, val - arr[ind]); } |
| 103 | + |
| 104 | + void add(int ind, T val) { |
| 105 | + arr[ind] += val; |
| 106 | + ind++; |
| 107 | + for (; ind <= size; ind += ind & -ind) { bit[ind] += val; } |
| 108 | + } |
| 109 | + |
| 110 | + T pref_sum(int ind) { |
| 111 | + ind++; |
| 112 | + T total = 0; |
| 113 | + for (; ind > 0; ind -= ind & -ind) { total += bit[ind]; } |
| 114 | + return total; |
| 115 | + } |
| 116 | +}; |
| 117 | +// EndCodeSnip |
67 | 118 |
|
68 | 119 | int main() {
|
69 |
| - cin.tie(0)->sync_with_stdio(0); |
70 |
| - cin >> n >> q; |
71 |
| - for (int i = 1; i <= n; i++) { |
72 |
| - cin >> x[i]; |
73 |
| - pref[i] = pref[i - 1] + x[i]; |
| 120 | + int arr_size; |
| 121 | + int query_num; |
| 122 | + std::cin >> arr_size >> query_num; |
| 123 | + vector<int> arr(arr_size); |
| 124 | + for (int &i : arr) { std::cin >> i; } |
| 125 | + vector<vector<pair<int, int>>> queries(arr_size); |
| 126 | + for (int q = 0; q < query_num; q++) { |
| 127 | + int start, end; |
| 128 | + std::cin >> start >> end; |
| 129 | + queries[start - 1].push_back({end - 1, q}); |
74 | 130 | }
|
75 |
| - x[n + 1] = INF; |
76 |
| - pref[n + 1] = pref[n] + x[n + 1]; |
77 |
| - for (int i = 1; i <= q; i++) { |
78 |
| - int a, b; |
79 |
| - cin >> a >> b; |
80 |
| - bckt[a].push_back({b, i}); |
| 131 | + |
| 132 | + vector<ll> pref_arr(arr_size + 1); |
| 133 | + for (int i = 0; i < arr_size; i++) { |
| 134 | + pref_arr[i + 1] = pref_arr[i] + arr[i]; |
81 | 135 | }
|
82 |
| - deque<int> stck = {n + 1}; |
83 |
| - for (int i = n; i; i--) { |
84 |
| - while (stck.size() && x[i] >= x[stck.front()]) { |
85 |
| - update(stck.front(), -contrib[stck.front()]); |
86 |
| - stck.pop_front(); |
| 136 | + |
| 137 | + vector<ll> ans(query_num); |
| 138 | + vector<pair<int, int>> maxes; |
| 139 | + BIT<ll> contrib(arr_size); |
| 140 | + for (int i = arr_size - 1; i >= 0; i--) { |
| 141 | + // update our stack |
| 142 | + while (!maxes.empty() && arr[i] >= maxes.back().first) { |
| 143 | + maxes.pop_back(); |
| 144 | + // no longer contributing anything- set it to 0 |
| 145 | + contrib.set(maxes.size(), 0); |
87 | 146 | }
|
88 |
| - contrib[i] = |
89 |
| - (stck.front() - 1 - i) * x[i] - (pref[stck.front() - 1] - pref[i]); |
90 |
| - update(i, contrib[i]); |
91 |
| - stck.push_front(i); |
92 |
| - for (pair<int, int> j : bckt[i]) { |
93 |
| - int pos = upper_bound(stck.begin(), stck.end(), j.first) - |
94 |
| - stck.begin() - 1; |
95 |
| - ans[j.second] = (pos ? query(i, stck[pos - 1]) : 0) + |
96 |
| - (j.first - stck[pos]) * x[stck[pos]] - |
97 |
| - (pref[j.first] - pref[stck[pos]]); |
| 147 | + |
| 148 | + // get the contribution of our new element |
| 149 | + int len = (maxes.empty() ? arr_size : maxes.back().second) - i; |
| 150 | + contrib.set(maxes.size(), (ll)arr[i] * len); |
| 151 | + maxes.push_back({arr[i], i}); |
| 152 | + |
| 153 | + for (const auto &[end, q] : queries[i]) { |
| 154 | + // binary search for where the query end is in the stack |
| 155 | + int lo = 0; |
| 156 | + int hi = maxes.size() - 1; |
| 157 | + int valid = -1; |
| 158 | + while (lo <= hi) { |
| 159 | + int mid = (lo + hi) / 2; |
| 160 | + if (maxes[mid].second <= end) { |
| 161 | + valid = mid; |
| 162 | + hi = mid - 1; |
| 163 | + } else { |
| 164 | + lo = mid + 1; |
| 165 | + } |
| 166 | + } |
| 167 | + |
| 168 | + // the contribution from most of the max elements |
| 169 | + ll sum1 = |
| 170 | + contrib.pref_sum(maxes.size() - 1) - contrib.pref_sum(valid); |
| 171 | + // the tail element mentioned in the editorial |
| 172 | + ll sum2 = (ll)(end - maxes[valid].second + 1) * maxes[valid].first; |
| 173 | + // the second term in the contribution formula |
| 174 | + ll pref_sub = pref_arr[end + 1] - pref_arr[i]; |
| 175 | + ans[q] = sum1 + sum2 - pref_sub; |
98 | 176 | }
|
99 | 177 | }
|
100 |
| - for (int i = 1; i <= q; i++) cout << ans[i] << '\n'; |
101 |
| - return 0; |
| 178 | + |
| 179 | + for (ll a : ans) { cout << a << '\n'; } |
102 | 180 | }
|
103 | 181 | ```
|
104 | 182 |
|
105 | 183 | </CPPSection>
|
106 |
| -
|
107 | 184 | </LanguageSection>
|
| 185 | +
|
| 186 | +</Spoiler> |
0 commit comments