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.
For a pair , do not start by deciding both ends. First ask what happens if the right endpoint chooses the prefix maximum versus the prefix minimum.
If , no earlier value can beat it. If , then earlier elements from older prefix-minimum blocks always beat it.
The real units are blocks where is constant. In the original permutation, the next block start is just the first position to the right with a smaller value, i.e. a next-smaller jump.
For a block of length after processed elements, if you choose prefix maxima first and then prefix minima, its contribution is This quadratic is concave; when , the best choice is boring but powerful: choose all prefix minima.
Only blocks with are annoying, and after one of them the processed length at least doubles, so there are of them. For the many blocks, binary-lift along the next-smaller chain and aggregate lengths using
The title is trolling you a bit. The clean way is to count inversions by looking at the right endpoint of each pair.
For a fixed subarray , write
For a pair :
So choices only interact inside blocks with equal prefix minimum. Blocks with different prefix minimum talk to each other in the dumbest possible way: a later minimum loses to every earlier element.
Split the query into consecutive blocks where is constant. Suppose a non-first block has length , and positions are before it. If positions in this block are chosen as prefix maximum, putting those choices first is optimal, because only max-before-min pairs inside the block matter.
The contribution is
This is a concave quadratic, so
is optimal. When , this gives , i.e. choose all prefix minima and gain inversions.
The first block has one special nuisance: at the first position, prefix minimum and maximum are equal. It contributes nothing inside that block, so for first-block length the answer is
For the whole permutation, define as the first index with , or the sentinel if it does not exist. This is found by a monotonic stack.
For a query , the prefix-minimum block starts are
until we leave the query. The block starting at has full length , except the last one may be cut by .
If a block has , then after processing it the processed length becomes . So this scary case happens only times. Nice. The rest are blocks with , where the contribution is just .
For several consecutive full boring blocks with lengths and initial processed length , their total contribution is
where . So we only need and for a chain segment.
Precompute binary lifting on the chain:
Since , all of this can be filled from right to left.
During a query, let be the processed length and the current block start. Put . Every block before has length , hence it is boring and can be aggregated with the formula above. Then process the stopping block directly using the quadratic. If the jump goes past , use to stop at the last block start inside the query.
Each direct block processing either ends the query, handles a block, or handles a block with ; in all cases there are only such steps. The extra border search costs another .
The preprocessing is per test case. Each query is with this straightforward lifting implementation, and the memory usage is . With , this is comfortably fine.
Reference checked while verifying the optimization: official Codeforces Round 1089 editorial.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int main() {
setIO();
constexpr int LOG = 21;
int tc;
cin >> tc;
while (tc--) {
int n, q;
cin >> n >> q;
vector<int> p(n);
for (int &x : p) cin >> x;
vector<int> nxt(n, n), st;
st.reserve(n);
for (int i = 0; i < n; i++) {
while (!st.empty() && p[i] < p[st.back()]) {
nxt[st.back()] = i;
st.pop_back();
}
st.push_back(i);
}
vector<array<int, LOG>> up(n + 1), pa(n + 1);
for (int i = 0; i <= n; i++) {
up[i].fill(n);
pa[i].fill(n);
}
vector<ll> sumLen(n + 1), sumSq(n + 1);
for (int i = n - 1; i >= 0; i--) {
int par = nxt[i];
int d = par - i;
sumLen[i] = sumLen[par] + d;
sumSq[i] = sumSq[par] + 1LL * d * d;
pa[i][0] = par;
for (int j = 1; j < LOG; j++) {
pa[i][j] = pa[pa[i][j - 1]][j - 1];
}
for (int j = 0; j < LOG; j++) {
up[i][j] = (d >= (1 << j) ? i : up[par][j]);
}
}
auto blockValue = [](ll before, ll len) -> ll {
ll x = (len - before) / 2;
if (x < 0) x = 0;
if (x > len) x = len;
return (before + x) * (len - x);
};
while (q--) {
int l, r;
cin >> l >> r;
--l;
int u = min(r, nxt[l]);
ll freeInsideFirst = u - l - 1LL;
ll x = freeInsideFirst / 2;
ll ans = x * (freeInsideFirst - x);
ll done = u - l;
while (u < r) {
int lg = (done <= 1 ? 0 : int(bit_width((unsigned long long) done)) - 1);
if (lg >= LOG) lg = LOG - 1;
int v = up[u][lg];
if (v > r) {
v = u;
for (int j = LOG - 1; j >= 0; j--) {
if (pa[v][j] <= r) v = pa[v][j];
}
}
ll total = sumLen[u] - sumLen[v];
ll sq = sumSq[u] - sumSq[v];
ans += done * total + (total * total - sq) / 2;
done += total;
u = v;
if (u < r) {
ll len = min(r, nxt[u]) - u;
ans += blockValue(done, len);
done += len;
u = min(r, nxt[u]);
}
}
cout << ans << '\n';
}
}
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int main() {
setIO();
constexpr int LOG = 21;
int tc;
cin >> tc;
while (tc--) {
int n, q;
cin >> n >> q;
vector<int> p(n);
for (int &x : p) cin >> x;
vector<int> nxt(n, n), st;
st.reserve(n);
for (int i = 0; i < n; i++) {
while (!st.empty() && p[i] < p[st.back()]) {
nxt[st.back()] = i;
st.pop_back();
}
st.push_back(i);
}
vector<array<int, LOG>> up(n + 1), pa(n + 1);
for (int i = 0; i <= n; i++) {
up[i].fill(n);
pa[i].fill(n);
}
vector<ll> sumLen(n + 1), sumSq(n + 1);
for (int i = n - 1; i >= 0; i--) {
int par = nxt[i];
int d = par - i;
sumLen[i] = sumLen[par] + d;
sumSq[i] = sumSq[par] + 1LL * d * d;
pa[i][0] = par;
for (int j = 1; j < LOG; j++) {
pa[i][j] = pa[pa[i][j - 1]][j - 1];
}
for (int j = 0; j < LOG; j++) {
up[i][j] = (d >= (1 << j) ? i : up[par][j]);
}
}
auto blockValue = [](ll before, ll len) -> ll {
ll x = (len - before) / 2;
if (x < 0) x = 0;
if (x > len) x = len;
return (before + x) * (len - x);
};
while (q--) {
int l, r;
cin >> l >> r;
--l;
int u = min(r, nxt[l]);
ll freeInsideFirst = u - l - 1LL;
ll x = freeInsideFirst / 2;
ll ans = x * (freeInsideFirst - x);
ll done = u - l;
while (u < r) {
int lg = (done <= 1 ? 0 : int(bit_width((unsigned long long) done)) - 1);
if (lg >= LOG) lg = LOG - 1;
int v = up[u][lg];
if (v > r) {
v = u;
for (int j = LOG - 1; j >= 0; j--) {
if (pa[v][j] <= r) v = pa[v][j];
}
}
ll total = sumLen[u] - sumLen[v];
ll sq = sumSq[u] - sumSq[v];
ans += done * total + (total * total - sq) / 2;
done += total;
u = v;
if (u < r) {
ll len = min(r, nxt[u]) - u;
ans += blockValue(done, len);
done += len;
u = min(r, nxt[u]);
}
}
cout << ans << '\n';
}
}
}