[TJOI2011] 书架(线段数优化dp + 单调栈)
problem
luogu-P1295
首先可以列出一個(gè)暴力 dpdpdp 轉(zhuǎn)移。
設(shè) f(i):f(i):f(i): 到 iii 為止劃分若干組,每組最大值的和 的最小值。
然后枚舉最后一組,即 iii 所在組的開頭 jjj,則 f(i)=min?{f(j?1)+max?j≤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: max?j≤k≤i{ak}\max_{j\le k\le i}\{a_k\}maxj≤k≤i?{ak?} 隨 jjj 的減小不降。
observation3:j\text{observation3}:jobservation3:j 的取值,因?yàn)?mmm 的限制,一定是段連續(xù)區(qū)間。我們可以用單調(diào)棧記錄下 jjj 取值的最小位置記為 pip_ipi?。
而一段連續(xù)的區(qū)間中選取最小值轉(zhuǎn)移,我們很容易想到線段樹優(yōu)化 dpdpdp。
但這里似乎還要處理一下 max?\maxmax 這個(gè)麻煩的部分。
管他的,先把線段樹建出來,然后對(duì)于 iii 而言,線段樹上每個(gè)點(diǎn) jjj 表示其做最后一組的開頭時(shí),前面所有組的最大值之和的最小值。
即線段樹上每個(gè)點(diǎn) jjj,都記錄 f(j?1)f(j-1)f(j?1)。
對(duì)于 max?\maxmax 部分,因?yàn)槠洳唤?#xff0c;我們可以找到最大的 jjj 滿足 aj>aia_j>a_iaj?>ai? 的位置,不妨記為 gig_igi?。這可以單調(diào)棧來做到。
也就是說,當(dāng) dpdpdp 枚舉到 iii 后,線段數(shù)上 [gi+1,i][g_i+1,i][gi?+1,i] 的位置做 jjj 轉(zhuǎn)移時(shí) max?\maxmax 部分的貢獻(xiàn)都是 aia_iai? 了。
這就是線段樹的區(qū)間覆蓋操作。
所以我們線段樹上不僅可以記錄 f(j?1)f(j-1)f(j?1) 還可以記錄其做轉(zhuǎn)移時(shí)的 max?\maxmax 貢獻(xiàn),以及二者相加的最小值,隨著 iii 的枚舉,激活 f(i?1)f(i-1)f(i?1),并區(qū)間覆蓋 max?\maxmax 貢獻(xiàn)即可。
#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; }總結(jié)
以上是生活随笔為你收集整理的[TJOI2011] 书架(线段数优化dp + 单调栈)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [CQOI2017] 小Q的表格(分块
- 下一篇: 【学习笔记】同余最短路