Skip to content

Commit 5ea7407

Browse files
Merge pull request #4101 from SansPapyrus683/increasing-array
increasing array queries
2 parents 9b29834 + fc3ed61 commit 5ea7407

File tree

3 files changed

+155
-77
lines changed

3 files changed

+155
-77
lines changed

content/4_Gold/PURS.mdx

+2-4
Original file line numberDiff line numberDiff line change
@@ -508,8 +508,6 @@ first glance.
508508
</Resource>
509509
</Resources>
510510
511-
#### Solution 1
512-
513511
```cpp
514512
#include <cassert>
515513
#include <iostream>
@@ -534,10 +532,10 @@ template <class T> class BIT {
534532
BIT(int size) : size(size), bit(size + 1), arr(size) {}
535533

536534
/** Sets the value at index ind to val. */
537-
void set(int ind, int val) { add(ind, val - arr[ind]); }
535+
void set(int ind, T val) { add(ind, val - arr[ind]); }
538536

539537
/** Adds val to the element at index ind. */
540-
void add(int ind, int val) {
538+
void add(int ind, T val) {
541539
arr[ind] += val;
542540
ind++;
543541
for (; ind <= size; ind += ind & -ind) { bit[ind] += val; }

content/4_Gold/Stacks.problems.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,8 @@
134134
"isStarred": false,
135135
"tags": ["Stack", "PURS"],
136136
"solutionMetadata": {
137-
"kind": "internal"
137+
"kind": "internal",
138+
"hasHints": true
138139
}
139140
},
140141
{

solutions/gold/cses-2416.mdx

+151-72
Original file line numberDiff line numberDiff line change
@@ -2,106 +2,185 @@
22
id: cses-2416
33
source: CSES
44
title: Increasing Array Queries
5-
author: Andi Qu
5+
author: Andi Qu, Kevin Sheng
66
---
77

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.
930

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>
1332

14-
First, let's think about how we'd answer a single query $(l, r)$. Consider the
15-
following algorithm:
33+
<Spoiler title="Solution">
1634

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
2236

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:
2644

2745
$$
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]
2947
$$
3048

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)$.
3450

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.
3852

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.
4055

41-
<LanguageSection>
56+
### Execution
4257

43-
<CPPSection>
58+
How do we utilize this?
4459

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.
4961

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.
5163

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.
5665

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*.
6067

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
67118

68119
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});
74130
}
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];
81135
}
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);
87146
}
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;
98176
}
99177
}
100-
for (int i = 1; i <= q; i++) cout << ans[i] << '\n';
101-
return 0;
178+
179+
for (ll a : ans) { cout << a << '\n'; }
102180
}
103181
```
104182
105183
</CPPSection>
106-
107184
</LanguageSection>
185+
186+
</Spoiler>

0 commit comments

Comments
 (0)