[TJOI2011] 书架(线段数优化dp + 单调栈)

problem

luogu-P1295

首先可以列出一个暴力 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(j1)+maxjki{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\} maxjki{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(j1)

对于 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(j1) 还可以记录其做转移时的 max ⁡ \max max 贡献,以及二者相加的最小值,随着 i i i 的枚举,激活 f ( i − 1 ) f(i-1) f(i1),并区间覆盖 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;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值