problem
luogu-P1295
首先可以列出一个暴力 dpdpdp 转移。
设 f(i):f(i):f(i): 到 iii 为止划分若干组,每组最大值的和 的最小值。
然后枚举最后一组,即 iii 所在组的开头 jjj,则 f(i)=min{f(j−1)+maxj≤k≤i{ak}}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) 的值随 iii 的增加不降。
observation2:\text{observation2}:observation2: maxj≤k≤i{ak}\max_{j\le k\le i}\{a_k\}maxj≤k≤i{ak} 随 jjj 的减小不降。
observation3:j\text{observation3}:jobservation3:j 的取值,因为 mmm 的限制,一定是段连续区间。我们可以用单调栈记录下 jjj 取值的最小位置记为 pip_ipi。
而一段连续的区间中选取最小值转移,我们很容易想到线段树优化 dpdpdp。
但这里似乎还要处理一下 max\maxmax 这个麻烦的部分。
管他的,先把线段树建出来,然后对于 iii 而言,线段树上每个点 jjj 表示其做最后一组的开头时,前面所有组的最大值之和的最小值。
即线段树上每个点 jjj,都记录 f(j−1)f(j-1)f(j−1)。
对于 max\maxmax 部分,因为其不降,我们可以找到最大的 jjj 满足 aj>aia_j>a_iaj>ai 的位置,不妨记为 gig_igi。这可以单调栈来做到。
也就是说,当 dpdpdp 枚举到 iii 后,线段数上 [gi+1,i][g_i+1,i][gi+1,i] 的位置做 jjj 转移时 max\maxmax 部分的贡献都是 aia_iai 了。
这就是线段树的区间覆盖操作。
所以我们线段树上不仅可以记录 f(j−1)f(j-1)f(j−1) 还可以记录其做转移时的 max\maxmax 贡献,以及二者相加的最小值,随着 iii 的枚举,激活 f(i−1)f(i-1)f(i−1),并区间覆盖 max\maxmax 贡献即可。
#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 0x7f7f7f7fvoid 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;
}