Search for a command to run...
Progressive hints first, then the full explanation and implementation when you're ready to cash out.
Review status
AI-generated and still unreviewed. Double-check the details before internalizing them.
Hints
Open only as much as you need to keep the solve alive.
A block only cares about two things: its last element and the minimum inside it. Any middle element that is not useful as a future minimum is probably dead weight.
Scan left to right and keep a stack of surviving values. When a new value appears, every previous survivor can be deleted without changing the answer. After this cleanup, the remaining array is strictly increasing.
On a strictly increasing array , the minimum of a block is just , so its cost is . Now the DP is only about choosing the next cut.
You never need an optimal partition containing a block with cost at least . Such a block can be split into two cheaper blocks with total cost no larger. The big-cost block is fake value; split it and move on.
So for each start , try only block costs . If the block has target cost , take it as far right as possible while , then transition to the first index after that block.
Core Cleanup
The annoying part of the original array is that block minimums can be buried anywhere. The first move is to delete all elements that are not strict suffix minimums.
A practical way to build the reduced array is:
The final is strictly increasing.
Why is this allowed? Suppose some earlier endpoint/minimum candidate is at least as large as a later element. Then the earlier one is never a better denominator than the later one, and cuts can be shifted/exchanged so it does not improve any optimal partition. Repeating this removes every dominated element. So .
For example, reduces to , whose answer is .
DP After Reduction
Now .
For any block , the minimum is , so
Let be the minimum cost to partition the suffix . Then
with .
Naively this is , which is not invited to the party.
Why Only Costs 1, 2, 3 Matter
Here is the key trick: there is always an optimal partition where every block has cost at most .
Take any block with cost . We can split it into two blocks with total cost at most .
Choose
Then:
Inside the block, cut near the last place where the value is still too small to make the suffix block cost at most . Because , the prefix block gets cost at most , and the suffix block gets cost at most . Total cost is at most .
So any block costing or more can be replaced by smaller-cost blocks without making the answer worse. Keep doing that until all block costs are , , or .
This is the whole problem, basically. Once you see this, the scary 2500 rating loses a lot of its teeth.
Transitions
For a fixed start and desired block cost , the farthest valid end satisfies
Since values are integers, this is equivalent to
If we are paying cost , we should extend the block as far as possible. Ending earlier cannot help, because it leaves more elements for the suffix while paying the same current cost. So the transition is:
Because is strictly increasing, that index can be found with upper_bound, giving total time. That is already plenty for .
Overflow Note
Values go up to , and we multiply by . That still fits in unsigned 64-bit but not signed long long. Use __int128 for the product. Tiny detail, huge WA if ignored. Classic Codeforces tax.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<ll> v;
v.reserve(n);
for (int i = 0; i < n; i++) {
ll x;
cin >> x;
while (!v.empty() && x <= v.back()) v.pop_back();
v.push_back(x);
}
int m = (int)v.size();
vector<int> dp(m + 1, n + 5);
dp[m] = 0;
for (int i = m - 1; i >= 0; i--) {
for (int c = 1; c <= 3; c++) {
__int128 lim = (__int128)v[i] * c;
int nxt = upper_bound(v.begin(), v.end(), lim,
[](__int128 value, ll elem) {
return value < (__int128)elem;
}) - v.begin();
dp[i] = min(dp[i], c + dp[nxt]);
}
}
cout << dp[0] << '\n';
}
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<ll> v;
v.reserve(n);
for (int i = 0; i < n; i++) {
ll x;
cin >> x;
while (!v.empty() && x <= v.back()) v.pop_back();
v.push_back(x);
}
int m = (int)v.size();
vector<int> dp(m + 1, n + 5);
dp[m] = 0;
for (int i = m - 1; i >= 0; i--) {
for (int c = 1; c <= 3; c++) {
__int128 lim = (__int128)v[i] * c;
int nxt = upper_bound(v.begin(), v.end(), lim,
[](__int128 value, ll elem) {
return value < (__int128)elem;
}) - v.begin();
dp[i] = min(dp[i], c + dp[nxt]);
}
}
cout << dp[0] << '\n';
}
return 0;
}