problem
首先可以列出一个暴力 d p dp dp 转移。
设 f ( i ) : f(i): f(i): 到 i i i 为止划分若干组,每组最大值的和 的最小值。
然后枚举最后一组,即 i i i 所在组的开头 j j j,则 f ( i ) = min { f ( j − 1 ) + max j ≤ k ≤ i { a k } } f(i)=\min\Big\{f(j-1)+\max_{j\le k\le i}\big\{a_k\big\}\Big\} f(i)=min{f(j−1)+maxj≤k≤i{ak}}。
observation1 : \text{observation1}: observation1: f ( i ) f(i) f(i) 的值随 i i i 的增加不降。
observation2 : \text{observation2}: observation2: max j ≤ k ≤ i { a k } \max_{j\le k\le i}\{a_k\} maxj≤k≤i{ak} 随 j j j 的减小不降。
observation3 : j \text{observation3}:j observation3:j 的取值,因为 m m m 的限制,一定是段连续区间。我们可以用单调栈记录下 j j j 取值的最小位置记为 p i p_i pi。
而一段连续的区间中选取最小值转移,我们很容易想到线段树优化 d p dp dp。
但这里似乎还要处理一下 max \max max 这个麻烦的部分。
管他的,先把线段树建出来,然后对于 i i i 而言,线段树上每个点 j j j 表示其做最后一组的开头时,前面所有组的最大值之和的最小值。
即线段树上每个点 j j j,都记录 f ( j − 1 ) f(j-1) f(j−1)。
对于 max \max max 部分,因为其不降,我们可以找到最大的 j j j 满足 a j > a i a_j>a_i aj>ai 的位置,不妨记为 g i g_i gi。这可以单调栈来做到。
也就是说,当 d p dp dp 枚举到 i i i 后,线段数上 [ g i + 1 , i ] [g_i+1,i] [gi+1,i] 的位置做 j j j 转移时 max \max max 部分的贡献都是 a i a_i ai 了。
这就是线段树的区间覆盖操作。
所以我们线段树上不仅可以记录 f ( j − 1 ) f(j-1) f(j−1) 还可以记录其做转移时的 max \max max 贡献,以及二者相加的最小值,随着 i i i 的枚举,激活 f ( i − 1 ) f(i-1) f(i−1),并区间覆盖 max \max max 贡献即可。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define maxn 100005
int n, m;
int h[maxn], sum[maxn], g[maxn], p[maxn], f[maxn];
stack < int > s;
queue < int > q;
namespace SGT {
struct node { int ans, minf, tag; }t[maxn << 2];
#define lson now << 1
#define rson now << 1 | 1
#define mid (l + r >> 1)
#define inf 0x7f7f7f7f
void build( int now, int l, int r ) {
t[now].ans = t[now].minf = t[now].tag = inf;
if( l == r ) return;
build( lson, l, mid );
build( rson, mid + 1, r );
}
void pushup( int now ) {
t[now].ans = min( t[lson].ans, t[rson].ans );
t[now].minf = min( t[lson].minf, t[rson].minf );
}
void pushdown( int now ) {
if( t[now].tag == inf ) return;
t[lson].ans = t[lson].minf + t[now].tag;
t[rson].ans = t[rson].minf + t[now].tag;
t[lson].tag = t[rson].tag = t[now].tag;
t[now].tag = inf;
}
void modify( int now, int l, int r, int L, int R, int val ) {
if( R < l or r < L ) return;
if( L <= l and r <= R ) {
t[now].tag = val;
t[now].ans = t[now].minf + val;
return;
}
pushdown( now );
modify( lson, l, mid, L, R, val );
modify( rson, mid + 1, r, L, R, val );
pushup( now );
}
void modify( int now, int l, int r, int pos ) {
if( l == r ) { t[now].minf = f[l - 1]; return; }
pushdown( now );
if( pos <= mid ) modify( lson, l, mid, pos );
else modify( rson, mid + 1, r, pos );
pushup( now );
}
int query( int now, int l, int r, int L, int R ) {
if( R < l or r < L ) return inf;
if( L <= l and r <= R ) return t[now].ans;
pushdown( now );
return min( query( lson, l, mid, L, R ), query( rson, mid + 1, r, L, R ) );
}
}
signed main() {
scanf( "%lld %lld", &n, &m );
for( int i = 1;i <= n;i ++ ) scanf( "%lld", &h[i] );
for( int i = 1;i <= n;i ++ ) sum[i] = sum[i - 1] + h[i];
for( int i = 1;i <= n;i ++ ) {
while( ! s.empty() and h[s.top()] <= h[i] ) s.pop();
if( ! s.empty() ) g[i] = s.top();
s.push( i );
}
for( int i = 1;i <= n;i ++ ) {
while( ! q.empty() and sum[i] - sum[q.front() - 1] > m ) q.pop();
if( ! q.empty() ) p[i] = q.front();
else p[i] = i;
q.push( i );
}
SGT :: build( 1, 1, n );
for( int i = 1;i <= n;i ++ ) {
SGT :: modify( 1, 1, n, i );
SGT :: modify( 1, 1, n, g[i] + 1, i, h[i] );
f[i] = SGT :: query( 1, 1, n, p[i], i );
}
printf( "%lld\n", f[n] );
return 0;
}