bzoj 2678 [Usaco2012 Open]Bookshelf dp+multiset

题面

题目传送门

解法

  • 显然有一个 O ( n 2 ) O(n^2) O(n2)的dp: f [ i ] = m i n ( f [ j − 1 ] + m a x ( h [ j ] , … , h [ i ] ) )   ( s [ i ] − s [ j − 1 ] ≤ L ) f[i] =min(f[j-1]+max(h[j],\dots,h[i]))\ (s[i]-s[j-1]\leq L) f[i]=min(f[j1]+max(h[j],,h[i])) (s[i]s[j1]L)
  • 那么我们考虑如何优化这个dp。
  • g [ j ] = m a x ( h [ j ] , … , h [ i ] ) g[j]=max(h[j],\dots,h[i]) g[j]=max(h[j],,h[i]),那么显然可以发现 j j j在满足要求的区间中 g [ j ] g[j] g[j]单调不升。
  • 那么我们不妨可以维护出 g [ j ] g[j] g[j]相同的区间是什么。显然可以发现,在 g [ j ] g[j] g[j]相同的区间 [ l , r ] [l,r] [l,r]中, f [ l ] f[l] f[l]一定是整个区间中 f f f最小的,那么我们可以直接使用 f [ i ] f[i] f[i]转移。
  • 因为 f f f在不同的区间中并不一定单调,所以我们需要使用一个multiset来维护这些区间中的答案。
  • 考虑如何维护这些区间,显然我们只要使用一个队列就可以实现。
  • 时间复杂度: O ( n log ⁡ n ) O(n\log n) O(nlogn)

【注意事项】

  • 同时,如果状态转移方程写成 f [ i ] = m i n ( f [ j ] + m a x ( h [ j + 1 ] , … , h [ i ] ) )   ( s [ i ] − s [ j ] ≤ L ) f[i]=min(f[j]+max(h[j+1],\dots,h[i]))\ (s[i]-s[j]\leq L) f[i]=min(f[j]+max(h[j+1],,h[i])) (s[i]s[j]L)的话,可能处理转移区间的时候会有很多细节,不是特别容易实现。

代码

#include <bits/stdc++.h>
#define ll long long
#define N 100010
using namespace std;
template <typename T> void chkmax(T &x, T y) {x = x > y ? x : y;}
template <typename T> void chkmin(T &x, T y) {x = x > y ? y : x;}
template <typename T> void read(T &x) {
	x = 0; int f = 1; char c = getchar();
	while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
	while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
struct Node {int l, r;} q[N];
int a[N], h[N]; ll sum[N], f[N];
int main() {
	int n, L; read(n), read(L);
	int l = 1, r = 0;
	multiset <ll> s;
	for (int i = 1; i <= n; i++) {
		read(h[i]), read(a[i]);
		sum[i] = sum[i - 1] + a[i]; int pos = i;
		while (l <= r && h[q[r].r] <= h[i])
			s.erase(s.find(f[q[r].l - 1] + h[q[r].r])), pos = q[r--].l;
		q[++r] = (Node) {pos, i}; s.insert(f[q[r].l - 1] + h[i]);
		while (l <= r && sum[i] - sum[q[l].l - 1] > L) {
			s.erase(s.find(f[q[l].l - 1] + h[q[l].r])), q[l].l++;
			if (q[l].l > q[l].r) l++; else s.insert(f[q[l].l - 1] + h[q[l].r]);
		}
		f[i] = *s.begin();
	}
	cout << f[n] << "\n";
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值