洛谷P3957 跳房子 题解 二分答案/DP/RMQ

题目链接:https://www.luogu.org/problem/P3957
这道题目我用到了如下算法:

  • 线段树求区间最大值;
  • 二分答案;
  • DP求每一次枚举答案g时是否能够找到 \(\ge k\) 的解法。

我们一开始用 \(x[i]\)\(s[i]\) 来表示到起点的距离以及第 \(i\) 个点的分值。
与此同时我们还要算上我们的起点,它满足性质 \(x[0] = s[0] = 0\) ,我们接下来的判断都是建立在这 \(1 + n\) 个点的基础上的。

check(g)

首先,我们假设 \(g\) 已经确定的情况下,如何判断是否有行走方案能够累计到 \(\ge k\)
这一步需要用到DP思想,我们定义状态 \(f[i]\) 表示从起点到第 \(i\) 个点所能够积累的最大分值,那么,状态转移方程为:
\(f[i] = \max( f[j] )\) ,其中 \(j\) 满足 \(x[i]-d-g \le x[j] \le \min( x[i]-1, x[i]-d+g )\)
并且我们可以发现这个范围的 \(j\) 必定在一个连续的区间 \([L, R]\) 内,所以我们可以用二分(lower_boundupper_bound 函数来快速获得 \(L\)\(R\)
然后我们需要知道区间 \([L,R]\) 范围内 \(x[j]\) 的最大值,这一步过程我是使用线段树来实现的(因为这里涉及单点更新及区间最值)。

二分答案

在编写完 check(g) 函数之后,我们便可以在区间 \([0, x[n] ]\) 范围内进行二分,进而找到满足要求的最小的答案。

实现代码如下:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 500050;
int n, d, k, x[maxn], s[maxn], f[maxn];

#define lson l, mid, (rt<<1)
#define rson mid+1, r, (rt<<1|1)
#define inf (1<<29)
int maxv[maxn<<2];
void sg_push_up(int rt) {
    maxv[rt] = max(maxv[rt<<1], maxv[rt<<1|1]);
}
void sg_build(int l, int r, int rt) {
    if (l >= r) maxv[rt] = -inf;
    else {
        int mid = (l + r) / 2;
        sg_build(lson);
        sg_build(rson);
        sg_push_up(rt);
    }
}
void sg_update(int p, int v, int l, int r, int rt) {
    if (l == r) maxv[rt] = v;
    else {
        int mid = (l + r) / 2;
        if (p <= mid) sg_update(p, v, lson);
        else sg_update(p, v, rson);
        sg_push_up(rt);
    }
}
int sg_query(int L, int R, int l, int r, int rt) {
    if (L <= l && r <= R) return maxv[rt];
    int mid = (l + r) / 2, tmp = -inf;
    if (L <= mid) tmp = max(tmp, sg_query(L, R, lson));
    if (R > mid) tmp = max(tmp, sg_query(L, R, rson));
    return tmp;
}

bool check(int g) {
    sg_build(0, n, 1);
    sg_update(0, 0, 0, n, 1);
    for (int i = 1; i <= n; i ++) {
        f[i] = -inf;
        int lid = lower_bound(x, x+i, x[i]-d-g) - x;
        int rid = upper_bound(x, x+i+1, min(x[i]-1, x[i]-d+g)) - x - 1;
        if (lid <= rid) {
            int tmp = sg_query(lid, rid, 0, n, 1);
            if (tmp != -inf) {
                f[i] = tmp + s[i];
                if (f[i] >= k) return true;
                sg_update(i, f[i], 0, n, 1);
            }
        }
    }
    return false;
}

int main() {
    cin >> n >> d >> k;
    for (int i = 1; i <= n; i ++) cin >> x[i] >> s[i];
    int L = 0, R = x[n], res = -1;
    while (L <= R) {
        int mid = (L + R) / 2;
        if (check(mid)) { res = mid; R = mid-1; }
        else L = mid + 1;
    }
    cout << res << endl;
    return 0;
}

作者:zifeiy

posted @ 2019-10-28 16:14  codedecision  阅读(416)  评论(0编辑  收藏  举报