「DP/DS Rec.」[TJOI2011] 书架

3 篇文章 0 订阅
2 篇文章 0 订阅

[TJOI2011] 书架

Description

给你一个长度为 n n n 的序列 h 1 , 2 , ⋯   , n h_{1,2,\cdots,n} h1,2,,n,需要将 h 1 , 2 , ⋯   , n h_{1,2,\cdots,n} h1,2,,n 分成若干段,满足每段数字之和都不超过 m m m,并最小化每段的最大值之和。

Solution

分析题面,首先易得一个 O ( n 3 ) {\mathcal O}(n^3) O(n3) 的 DP 状态转移方程:

f i f_i fi 表示已经分好 i i i 个数字的最小代价,转移时枚举这一段的开头 j j j,将 [ j , i ] [j,i] [j,i] 作为新的一段,则有

f i = min ⁡ { f j − 1 + max ⁡ k = j i { h k }   ∣ ∑ k = j i h k ≤ m } f_i=\min\{f_{j-1}+\max^i_{k=j}\{h_k\} \ |\sum_{k=j}^i h_k \le m\} fi=min{fj1+k=jmaxi{hk} k=jihkm}

结合单调栈可优化到 O ( n 2 ) {\mathcal O}(n^2) O(n2),期望得分 30 p t s 30{\rm pts} 30pts

考虑进一步优化。

显然,对于一个给定的 i i i,当 j j j 单增时, max ⁡ k = j i { h k } \max\limits^i_{k=j}\{h_k\} k=jmaxi{hk} 单调不增, f j − 1 f_{j-1} fj1 单调不降。
当枚举到 i i i 时, f j − 1 f_{j-1} fj1 不会再改变,考虑 h i h_i hi max ⁡ k = j i { h k } \max\limits^i_{k=j}\{h_k\} k=jmaxi{hk} 的影响。

如图,设 i i i 左侧第一个满足 h > h i h>h_i h>hi 的位置为 l t i {\rm lt}_i lti,显然对于 max ⁡ k = j i { h k }   ∣   j > l t i \max\limits^i_{k=j}\{h_k\}\ |\ j>{\rm lt}_i k=jmaxi{hk}  j>lti 都会变为 h i h_i hi

max ⁡ k = j i { h k } \max\limits^i_{k=j}\{h_k\} k=jmaxi{hk} 可用支持区间赋值的数据结构进行维护,转移 f i f_i fi 时,需要进行区间查询,考虑线段树。


线段树维护位置 j j j f j − 1 f_{j-1} fj1 f j − 1 + max ⁡ k = j i { h k } f_{j-1}+\max\limits^i_{k=j}\{h_k\} fj1+k=jmaxi{hk},当枚举到一个新的 h i h_i hi 时:

  • 单点修改,更新位置 j = i j=i j=i 时的 f j − 1 f_{j-1} fj1
  • 根据 h i h_i hi 更新区间 [ l t i + 1 , i ] [{\rm lt}_i+1,i] [lti+1,i] f j − 1 + max ⁡ k = j i { h k } f_{j-1}+\max\limits^i_{k=j}\{h_k\} fj1+k=jmaxi{hk}
  • 二分得到第一个不满足 ∑ k = j i h k ≤ m \sum\limits_{k=j}^i h_k \le m k=jihkm 的位置 t m p \rm tmp tmp,则 j ∈ [ t m p + 1 , i ] j \in [{\rm tmp}+1,i] j[tmp+1,i]
  • 查询 [ t m p + 1 , i ] [{\rm tmp}+1,i] [tmp+1,i] 中最小的 f j − 1 + max ⁡ k = j i { h k } f_{j-1}+\max\limits^i_{k=j}\{h_k\} fj1+k=jmaxi{hk}

时间复杂度 O ( n 3 ) → O ( n 2 ) → O ( n log ⁡ n ) {\mathcal O}(n^3) \rightarrow {\mathcal O}(n^2) \rightarrow {\mathcal O }(n \log n) O(n3)O(n2)O(nlogn),期望得分 100 p t s 100 \rm pts 100pts

Code

#define int long long
int n,m,top,a[100001],sum[100001],s[100001],lt[100001],f[100001],v[100001<<2],pre[100001<<2],tag[100001<<2];

void push_up(int x) {
	v[x]=min(v[x<<1],v[x<<1|1]);
	pre[x]=min(pre[x<<1],pre[x<<1|1]);
}

void push_down(int x) {
	if (tag[x]==0x7f7f7f7f) return;
	v[x<<1]=pre[x<<1]+tag[x];
	v[x<<1|1]=pre[x<<1|1]+tag[x];
	tag[x<<1]=tag[x];
	tag[x<<1|1]=tag[x];
	tag[x]=0x7f7f7f7f;
}

void build(int x,int l,int r) {
	if (l==r) {
		v[x]=pre[x]=tag[x]=0x7f7f7f7f;
		return;
	}
	int mid=(l+r)>>1;
	build(x<<1,l,mid);
	build(x<<1|1,mid+1,r);
	push_up(x);
}

void update(int x,int l,int r,int L,int R,int k) {
	if (L<=l && r<=R) {
		v[x]=pre[x]+k;
		tag[x]=k;
		return;
	}
	push_down(x);
	int mid=(l+r)>>1;
	if (L<=mid) update(x<<1,l,mid,L,R,k);
	if (R>mid) update(x<<1|1,mid+1,r,L,R,k);
	push_up(x);
}

void modify(int x,int l,int r,int pos) {
	if (l==r) {
		v[x]=0x7f7f7f7f;
		pre[x]=f[pos-1];
		return;
	}
	push_down(x);
	int mid=(l+r)>>1;
	if (pos<=mid) modify(x<<1,l,mid,pos);
	else modify(x<<1|1,mid+1,r,pos);
	push_up(x);
}

int query(int x,int l,int r,int L,int R) {
	if (L<=l && r<=R) return v[x];
	push_down(x);
	int mid=(l+r)>>1,sum=0x7f7f7f7f;
	if (L<=mid) sum=min(sum,query(x<<1,l,mid,L,R));
	if (R>mid) sum=min(sum,query(x<<1|1,mid+1,r,L,R));
	return sum;
}

signed main() {
	scanf("%lld%lld",&n,&m);
	for (int i=1;i<=n;i++) {
		scanf("%lld",&a[i]);
		sum[i]=sum[i-1]+a[i];
	}
	s[++top]=1;
	for (int i=2;i<=n;i++) {
		while (top && a[s[top]]<a[i]) top--;
		if (top) lt[i]=s[top];
		s[++top]=i;
	}
	build(1,1,n);
	for (int i=1;i<=n;i++) {
		modify(1,1,n,i);
		if (lt[i]<i) update(1,1,n,lt[i]+1,i,a[i]);
		int tmp=lower_bound(sum,sum+i+1,sum[i]-m)-sum;
		if (tmp<i) f[i]=query(1,1,n,tmp+1,i);
	}
	return !printf("%lld",f[n]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值