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.
Look at one fixed row . The water cells in that row form contiguous intervals separated by columns with . What does a drain inside one of those intervals collect?
For a chosen drain column, placing the drain higher is never useful. Put it at the lowest water tile, row . So a drain is basically just choosing a column.
As increases, water intervals only merge; they never split. That means the row-components form a laminar family. Translation: there is a tree hiding in the dirt.
For an interval with upper height , let . The whole interval is one component for exactly rows, contributing if at least one chosen drain lies inside it.
Recursively split at every position where . Do DP with capacities : combine child intervals by tiny knapsack, then add the current component weight to and .
At row , column contains water iff . So the water cells in that row are split into maximal contiguous intervals.
If a drain's column lies inside one such interval, then every water tile in that interval can move horizontally to the drain's column, then move down to the drain. So at each row, a drain collects exactly the whole component containing its column.
Also, if we choose column , putting the drain higher is strictly worse. Move it down to row : every tile that could reach the old drain can continue downward in the same column. So each drain is just a chosen column.
As increases, more columns become water, so components only merge. This gives a laminar interval structure.
Consider some interval that is known to be surrounded by barriers until height . Let
For every row with , all columns in are water, so is one component. That component contributes
water tiles if at least one selected drain column lies inside .
For rows , every column with height is still dirt, so those columns split into independent smaller intervals. We recurse on the maximal segments between positions where , using upper height .
This is basically a max-Cartesian-tree decomposition, but we do not need to explicitly build the tree. Just recursively split by the maximum. Dirt does the divide-and-conquer for us. Very considerate dirt.
Let return , where is the maximum water collected inside this recursive interval using at most drains placed in .
Inside :
Now the key transition is simple:
and for ,
Why can we always add when ? Because either some drain is already placed in a child, hence it lies inside , or we can place one drain on a maximum-height column of this interval to collect the current component. If , adding it is harmless anyway.
The final answer is .
Every water tile belongs to exactly one row-component at its row. Our recursive decomposition assigns that component to exactly one interval node: the node whose height band contains that row.
A selected drain column collects a node's whole component iff the selected column lies inside that node's interval. Therefore, maximizing drained water is exactly the same as choosing up to two columns to maximize the total weight of interval nodes containing at least one chosen column.
The children of any interval are disjoint, so once the current component weight is handled, the remaining optimization is just distributing , , or drains among independent child intervals. The knapsack transition checks all such distributions, so it is exhaustive. Since the current node only cares whether at least one drain exists inside it, adding for is exactly right.
Thus the DP computes the optimal value for every recursive interval, and the root gives the optimal answer for the whole grid.
Each recursive call scans its interval to find the maximum and split children. In the worst case, the total scanned length is . The DP capacity is only , so child merging is constant-factor tiny.
Across all test cases, , so this easily fits.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
array<ll, 3> solveInterval(const vector<ll>& a, int l, int r, ll upper) {
if (l > r) return {0, 0, 0};
ll mx = a[l];
for (int i = l; i <= r; i++) mx = max(mx, a[i]);
ll weight = (upper - mx) * (ll)(r - l + 1);
array<ll, 3> best{0, 0, 0};
int i = l;
while (i <= r) {
if (a[i] == mx) {
i++;
continue;
}
int j = i;
while (j <= r && a[j] != mx) j++;
auto child = solveInterval(a, i, j - 1, mx);
array<ll, 3> nxt{0, 0, 0};
for (int cap = 0; cap <= 2; cap++) {
for (int take = 0; take <= cap; take++) {
nxt[cap] = max(nxt[cap], best[cap - take] + child[take]);
}
}
best = nxt;
i = j;
}
array<ll, 3> dp{0, 0, 0};
dp[1] = weight + best[1];
dp[2] = weight + best[2];
return dp;
}
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n;
ll h;
cin >> n >> h;
vector<ll> a(n);
for (ll& x : a) cin >> x;
cout << solveInterval(a, 0, n - 1, h)[2] << '\n';
}
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
array<ll, 3> solveInterval(const vector<ll>& a, int l, int r, ll upper) {
if (l > r) return {0, 0, 0};
ll mx = a[l];
for (int i = l; i <= r; i++) mx = max(mx, a[i]);
ll weight = (upper - mx) * (ll)(r - l + 1);
array<ll, 3> best{0, 0, 0};
int i = l;
while (i <= r) {
if (a[i] == mx) {
i++;
continue;
}
int j = i;
while (j <= r && a[j] != mx) j++;
auto child = solveInterval(a, i, j - 1, mx);
array<ll, 3> nxt{0, 0, 0};
for (int cap = 0; cap <= 2; cap++) {
for (int take = 0; take <= cap; take++) {
nxt[cap] = max(nxt[cap], best[cap - take] + child[take]);
}
}
best = nxt;
i = j;
}
array<ll, 3> dp{0, 0, 0};
dp[1] = weight + best[1];
dp[2] = weight + best[2];
return dp;
}
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n;
ll h;
cin >> n >> h;
vector<ll> a(n);
for (ll& x : a) cin >> x;
cout << solveInterval(a, 0, n - 1, h)[2] << '\n';
}
}