P5095 [USACO12OPEN] Bookshelf S

27 篇文章 0 订阅
10 篇文章 0 订阅

P5095 [USACO12OPEN] Bookshelf S

题目描述

Farmer John 闲来无事的时候总喜欢坐下来看书。这些年来,他一共收集了 N N N 本书( 1 ≤ N ≤ 2000 1 \leq N \leq 2000 1N2000),他打算搭一共新的书架来装这些书。

每本书都有个宽度 w i w_i wi 和高度 h i h_i hi,书必须按顺序来摆放(即同一层书架摆的书必须是连续的一个区间)。每层书架的总宽度不能超过 L L L 1 ≤ L ≤ 1 0 9 1 \leq L \leq 10^9 1L109),每层书架的高度等于这一层最高的书的高度,整个书架的高度等于每层书架的高度之和。

现在请你帮 FJ 求出书架高度的最小值。

输入格式

第一行两个整数 N , L N,L N,L

接下来 N N N 行,每行两个整数 h i , w i h_i,w_i hi,wi 1 ≤ h i ≤ 1 0 6 1 \leq h_i \leq 10^6 1hi106 1 ≤ w i ≤ L 1 \leq w_i \leq L 1wiL)。

输出格式

输出一个整数,书架高度最小值。

样例 #1

样例输入 #1

5 10
5 7
9 2
8 5
13 2
3 8

样例输出 #1

21

提示

第一层放第一本书,第二层放第二,三,四本书,第三层放第五本书,总高度为 5 + 13 + 3 = 21 5+13+3=21 5+13+3=21,可以证明不存在更优的方案。

思路

盲猜dp

一开始是两重的数组,要用线段树和 r m q rmq rmq 维护还不能过。

后面发现其实最终的层数和答案关系不大,关键的是高度

所以我们设 d p i dp_i dpi 表示放下前 i i i 本书的最小高度

那么
d p i = M I N j = 1 i − 1 d p j + M A X k = j i h k dp_i = MIN_{j = 1}^{i - 1} dp_j + MAX_{k = j}^ih_k dpi=MINj=1i1dpj+MAXk=jihk
考试时我用了 r m q rmq rmq 维护后面的 h h h ,其实不用,只要 j j j 从后枚举维护最大值就好了。

赛时 code

#include <bits/stdc++.h>
#define fu(x , y , z) for(int x = y ; x <= z ; x ++)
#define fd(x , y , z) for(int x = y ; x >= z ; x --)
#define LL long long
using namespace std;
const int N = 2005;
LL l , h[N] , w[N] , sq[35] , rmq[N][35] , ans , s[N] , dp[N];
int n;
void pre () {
    sq[0] = 1;
    for (int i = 1 ; i <= 30 ; i ++) sq[i] = sq[i - 1] * 2;
    fu (i , 1 , n) rmq[i][0] = h[i];
    fu (j , 1 , 30) {
        fu (i , 1 , n) {
            if (i + sq[j] - 1 > n) break;
            rmq[i][j] = max (rmq[i][j - 1] , rmq[i + sq[j - 1]][j - 1]);
        }
    }
}
LL find (int x , int y) {
    int lst = 30 , z;
    LL ansf = 0;
    while (x <= y) {
        for (int i = lst ; i >= 0 ; i --) {
            if (x + sq[i] - 1 > y) continue;
            ansf = max (ansf , rmq[x][i]);
            x = x + sq[i];
            z = i;
        }
        lst = z;
    }
    ansf = max (ansf , h[y]);
    return ansf;
}
int main () {
    scanf ("%d%lld" , &n , &l);
    fu (i , 1 , n)
        scanf ("%lld%lld" , &h[i] , &w[i]);
    pre ();
    fu (i , 1 , n) s[i] = s[i - 1] + w[i];
    fu (i , 1 , n) dp[i] = 1e18 + 5;
    fu (i , 1 , n) {
        fd (j , i - 1 , 0) {
            if (s[i] - s[j] > l) break;
            dp[i] = min (dp[i] , dp[j] + find (j + 1 , i));
        }
    }
    printf ("%lld" , dp[n]);
    return 0;
}

别人code

#include <bits/stdc++.h>
using namespace std;
const int N = 2002;
int n, m, p, k, ans, sum, tot, cnt, a[N], b[N], f[N], c[N], l;
int main()
{
	std::cin>> n >> l;
	for(int i = 1; i <= n; i++)
	{
		std::cin >> a[i] >> b[i];
		c[i] = c[i - 1] + b[i];
		f[i] = 1e9;
	}
	for(int i = 1; i <= n; i++)
	{
		f[i] = f[i - 1] + a[i];
		tot = a[i];
		for(int j = i - 1; j >= 1; j--)
		{
			if(c[i] - c[j - 1] > l)break;
			tot = max(tot, a[j]);
			f[i] = min(f[i], f[j - 1] + tot);
		}
	}
	cout << f[n];
	return 0;
}

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值