Skip to content

Commit cd6103e

Browse files
Best Subsequence Editorial
1 parent 82f7021 commit cd6103e

File tree

2 files changed

+183
-2
lines changed

2 files changed

+183
-2
lines changed

content/extraProblems.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -2757,8 +2757,8 @@
27572757
"isStarred": false,
27582758
"tags": [],
27592759
"solutionMetadata": {
2760-
"kind": "USACO",
2761-
"usacoId": "1498"
2760+
"kind": "internal",
2761+
"hasHints": true
27622762
}
27632763
},
27642764
{

solutions/orphaned/usaco-1498.mdx

+181
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
---
2+
id: usaco-1498
3+
source: Gold
4+
title: The Best Subsequence
5+
author: Justin Ji
6+
---
7+
8+
[Official Analysis (C++)](https://usaco.org/current/data/sol_prob2_gold_feb25.html)
9+
10+
<Spoiler title="Hint 1">
11+
12+
Let's assume we have some black box function that counts the number of ones in the range $[l, r]$. Let's consider two
13+
cases: one where the number of ones in our range is less than $K$, and
14+
if the number of ones is greater than or equal to $K$. How can we
15+
handle these two cases?
16+
17+
</Spoiler>
18+
19+
<Spoiler title="Hint 2">
20+
21+
If the number of ones is $\geq K$, then our subsequence can consist of
22+
all ones. Otherwise, we need to let some amount of zeros into our subsequence.
23+
24+
Let's say the number of ones in our range is $X$. Then, we need to let
25+
$K-X$ zeros into our range, and we want the first zero to come as far
26+
back as possible. What does this correspond to in our array?
27+
28+
</Spoiler>
29+
30+
<Spoiler title="Solution">
31+
32+
As denoted in the hints, let $X$ be the number of ones in our range.
33+
34+
### Case 1: $X \geq K$
35+
36+
Our entire subsequence is ones, so the answer is $2^k - 1$.
37+
38+
### Case 2: $X < K$
39+
40+
It's always optimal to take the smallest suffix of $[l, r]$ that
41+
has $K - X$ zeros. To intuitively understand this, consider the fact that
42+
we want to maximize the first position where we have a zero in our subsequence. Given that we need to take at least $K-X$ zeros, we at a
43+
minimum should take the last $K-X$ zeros in our range. Because we are
44+
trying to maximize our resulting subsequence, it's also optimal to take the ones inside the suffix we end up taking.
45+
46+
### Computing the Result
47+
48+
To compute $2^k$ efficiently, we use
49+
[modular exponentiation](/gold/modular). This allows us to handle case 1
50+
quite easily.
51+
52+
As mentioned in the hints, we want some function to efficiently find the
53+
number of ones in a given range. Let's first simplify this problem
54+
to counting the number of ones in a prefix. We can do this by coordinate
55+
compressing our values, and then calculating prefix sums of the number of ones in our prefix. Careful implementation is required here, as we have to make sure the last interval in our prefix sum is not overcounted.
56+
57+
We can reduce the problem of computing the hash of a range to finding the
58+
hash of a prefix. For every interval, we store the hash of the entire prefix that includes this interval. Make sure to handle intervals that
59+
are partially contained in our prefix carefully. Then, the hash of $[l, r]$ is:
60+
61+
$$
62+
\texttt{pref\_hash}(r) - 2^{r - l + 1} \cdot \texttt{pref\_hash}(l - 1)
63+
$$
64+
65+
To choose the suffix of our range that we hash, we need to find the
66+
smallest suffix that has $K - X$ zeros in it. This is equivalent to finding the last indice $m$ where the number of zeros in $[m, r]$ is $\geq K-X$. The easiest way to implement this is with binary search,
67+
although it is possible to cut a log factor by finding the interval
68+
our indice $m$ is in using lower bound on a prefix sum of zeros.
69+
70+
## Implementation
71+
72+
**Time Complexity:** $\mathcal{O}(Q\log^2N)$
73+
74+
<LanguageSection>
75+
<CPPSection>
76+
77+
```cpp
78+
#include <bits/stdc++.h>
79+
using namespace std;
80+
81+
using ll = long long;
82+
83+
constexpr int MOD = 1e9 + 7;
84+
85+
int modpow(int base, int exp) {
86+
int res = 1;
87+
for (; exp; exp /= 2, base = (1ll * base * base) % MOD) {
88+
if (exp & 1) { res = (1ll * res * base) % MOD; }
89+
}
90+
return res;
91+
}
92+
93+
int main() {
94+
int n, m, q;
95+
cin >> n >> m >> q;
96+
vector<int> vals = {0, INT_MAX};
97+
vector<array<int, 2>> upds(m);
98+
for (auto &[l, r] : upds) {
99+
cin >> l >> r;
100+
r++;
101+
vals.push_back(l);
102+
vals.push_back(r);
103+
}
104+
105+
sort(begin(vals), end(vals));
106+
vals.erase(unique(begin(vals), end(vals)), end(vals));
107+
108+
const auto id = [&](int x) -> int {
109+
return lower_bound(begin(vals), end(vals), x) - begin(vals);
110+
};
111+
112+
const int range_len = vals.size();
113+
vector<int> diff(range_len);
114+
for (const auto &[l, r] : upds) {
115+
diff[id(l)]++;
116+
diff[id(r)]--;
117+
}
118+
119+
vector<int> pref(range_len);
120+
for (int i = 0; i + 1 < range_len; i++) {
121+
diff[i + 1] += diff[i];
122+
diff[i] &= 1;
123+
pref[i + 1] = pref[i] + diff[i] * (vals[i + 1] - vals[i]);
124+
}
125+
126+
vector<int> pref_hash(range_len);
127+
for (int i = 0; i + 1 < range_len; i++) {
128+
const int len = vals[i + 1] - vals[i];
129+
const int pw2 = modpow(2, len);
130+
pref_hash[i + 1] = diff[i] * (pw2 - 1 + MOD) % MOD
131+
+ (1ll * pref_hash[i] * pw2) % MOD;
132+
pref_hash[i + 1] %= MOD;
133+
}
134+
135+
const auto range_hash = [&](int l, int r) -> int {
136+
const auto get_pref = [&](int x) -> int {
137+
const int pos = upper_bound(begin(vals), end(vals), x) - begin(vals) - 1;
138+
const int pw2 = modpow(2, x - vals[pos] + 1);
139+
return (1ll * pref_hash[pos] * pw2 + diff[pos] * (pw2 - 1)) % MOD;
140+
};
141+
142+
ll raw = get_pref(r) - 1ll * modpow(2, r - l + 1) * get_pref(l - 1);
143+
return (raw % MOD + MOD) % MOD;
144+
};
145+
146+
const auto get_ones = [&](int l, int r) -> int {
147+
const auto get_pref = [&](int x) -> int {
148+
const int pos = upper_bound(begin(vals), end(vals), x) - begin(vals);
149+
return pref[pos - 1] + diff[pos - 1] * (x - vals[pos - 1] + 1);
150+
};
151+
152+
return get_pref(r) - get_pref(l - 1);
153+
};
154+
155+
for (int t = 0; t < q; t++) {
156+
int l, r, k;
157+
cin >> l >> r >> k;
158+
const int num_ones = get_ones(l, r);
159+
if (num_ones >= k) {
160+
cout << modpow(2, k) - 1 << "\n";
161+
} else {
162+
int lo = l, hi = r;
163+
while (lo < hi) {
164+
int mid = (lo + hi + 1) / 2;
165+
bool works = (r - mid + 1) - get_ones(mid, r) >= k - num_ones;
166+
works ? lo = mid : hi = mid - 1;
167+
}
168+
169+
const int suffix_hash = range_hash(lo, r);
170+
const int suffix_ones = get_ones(lo, r);
171+
const ll res = suffix_hash + modpow(2, k) - modpow(2, k - (num_ones - suffix_ones));
172+
cout << (res % MOD + MOD) % MOD << "\n";
173+
}
174+
}
175+
}
176+
```
177+
178+
</CPPSection>
179+
</LanguageSection>
180+
181+
</Spoiler>

0 commit comments

Comments
 (0)