P1848 [USACO12OPEN]Bookshelf G(线段树优化 DP)

P1848 [USACO12OPEN]Bookshelf G

n n n间物品,每个物品有两个属性 W i , H i W_i, H_i Wi,Hi,宽度跟高度,要求把这 n n n件物品划分成若干连续的组,每组内 ∑ W i ≤ L \sum\limits W_i \leq L WiL,并且要求最小化每组最大高度之和。

f [ i ] f[i] f[i]表示以 i i i为结尾的代价,则有:
f [ i ] = m i n ( f [ j ] + m a x j ≤ k ≤ i h [ k ] ) [ ∑ k = j i w [ i ] ≤ L ] f[i] = min(f[j] + max_{j \leq k \leq i} h[k])[\sum_{k = j} ^{i} w[i] \leq L]\\ f[i]=min(f[j]+maxjkih[k])[k=jiw[i]L]
容易发现这是可以 O ( n 2 ) O(n ^ 2) O(n2)转移的,考虑如何优化,

对于某个 f [ i ] f[i] f[i]来说,以其 h [ i ] h[i] h[i]作为最大值最多可以向左拓展的点,我们可以单调栈求出来,再利用二分查找,我们可以从 i i i点向左拓展出一个点,满足 ∑ w [ i ] ≤ L \sum w[i] \leq L w[i]L.

之后我们只要在区间上查找最小值,作为当前点的答案,更新即可,

#include <bits/stdc++.h>
#define mid (l + r >> 1)
#define lson rt << 1, l, mid
#define rson rt << 1 | 1, mid + 1, r
#define ls rt << 1
#define rs rt << 1 | 1

using namespace std;

const int N = 1e6 + 10;

long long ans[N], sum[N], minn[N << 2], mans[N << 2], lazy[N << 2], f[N];

int n, m, h[N], w[N], stk[N], pre[N], top;

void push_down(int rt) {
  if (lazy[rt]) {
    mans[ls] = minn[ls] + lazy[rt];
    mans[rs] = minn[rs] + lazy[rt];
    lazy[ls] = lazy[rs] = lazy[rt];
    lazy[rt] = 0;
  }
}

void push_up(int rt) {
  minn[rt] = min(minn[ls], minn[rs]);
  mans[rt] = min(mans[ls], mans[rs]);
}

void update(int rt, int l, int r, int x) {
  if (l == r) {
    mans[rt] = 0x3f3f3f3f3f3f3f3f, minn[rt] = f[x - 1];
    return ;
  }
  push_down(rt);
  if (x <= mid) {
    update(lson, x);
  }
  else {
    update(rson, x);
  }
  push_up(rt);
}

void update(int rt, int l, int r, int L, int R, int x) {
  if (l >= L && r <= R) {
    lazy[rt] = x, mans[rt] = minn[rt] + x;
    return ;
  }
  push_down(rt);
  if (L <= mid) {
    update(lson, L, R, x);
  }
  if (R > mid) {
    update(rson, L, R, x);
  }
  push_up(rt);
}

long long query(int rt, int l, int r, int L, int R) {
  if (l >= L && r <= R) {
    return mans[rt];
  }
  push_down(rt);
  long long ans = 0x3f3f3f3f3f3f3f3f;
  if (L <= mid) {
    ans = min(ans, query(lson, L, R));
  }
  if (R > mid) {
    ans = min(ans, query(rson, L, R));
  }
  return ans;
}

int main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
  scanf("%d %d", &n, &m);
  for (int i = 1; i <= n; i++) {
    scanf("%d %d", &h[i], &w[i]);
    sum[i] = sum[i - 1] + w[i];
  }
  stk[++top] = 1;
  for (int i = 2; i <= n; i++) {
    while (top && h[i] > h[stk[top]]) {
      top--;
    }
    pre[i] = stk[top], stk[++top] = i;
  }
  for (int i = 1; i <= n; i++) {
    update(1, 1, n, i);
    update(1, 1, n, pre[i] + 1, i, h[i]);
    int l = lower_bound(sum, sum + 1 + n, sum[i] - m) - sum;
    f[i] = query(1, 1, n, l + 1, i);
  }
  printf("%lld\n", f[n]);
  return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个长度为 $n$ 的书架,每本书有一个高度 $h_i$。现在你可以进行以下两种操作: - 将一本书放在书架的最左边或最右边,花费为 $c_1$。 - 将一本高度为 $h_i$ 的书放在一本高度为 $h_j$ 的书的上面,花费为 $c_2$。 现在你需要将书架上的书按照高度从小到大排列,求最小花费。 输入格式 第一行包含三个整数 $n,c_1,c_2$。 第二行包含 $n$ 个整数 $h_i$。 输出格式 输出一个整数,表示最小花费。 数据范围 $1\leq n\leq 200,1\leq c_1,c_2\leq 10^9,1\leq h_i\leq 10^9$ 输入样例 5 1 2 3 1 4 2 5 输出样例 6 算法1 (动态规划) $O(n^2)$ 首先考虑一个朴素的 dp,设 $f_{i,j}$ 表示前 $i$ 本书已经排好序,第 $i+1$ 本书放在第 $j$ 个位置的最小花费。 状态转移方程为: $$ f_{i,j}=\min\{f_{i-1,k}+c_1\}+\begin{cases}&\text{if }h_{i+1}>h_j\\c_2&\text{otherwise}\end{cases} $$ 其中 $k$ 取遍 $1\sim i$,表示将第 $i+1$ 本书放在第 $k$ 个位置。 时间复杂度 $O(n^3)$ C++ 代码 算法2 (单调队列优化) $O(n^2)$ 考虑优化上述 dp,发现状态转移方程中的 $\min$ 操作可以用单调队列优化,具体来说,我们维护一个单调递增的队列 $q$,其中 $q_i$ 表示第 $i$ 个位置的最小花费,那么对于状态 $f_{i,j}$,我们只需要找到 $q$ 中第一个大于等于 $f_{i-1,k}+c_1$ 的位置 $p$,然后 $f_{i,j}=q_p+\begin{cases}&\text{if }h_{i+1}>h_j\\c_2&\text{otherwise}\end{cases}$。 时间复杂度 $O(n^2)$ C++ 代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值