北京集训队2016 Day4 超级跳

原题目

问题描述

  小H最近迷上了一款叫做“超级跳”的休闲游戏。
  游戏中,有n层云朵。你控制一个小人,出生在第n层云朵上,目标是不断向下跳,最终抵达地面。为了方便,这里对游戏做一些简化。
  如果建立平面坐标系,可以将每层云朵抽象成一条平行于x轴的线段。第i层云朵可以抽象成一条纵坐标等于i,左右端点的横坐标分别是li和ri的线段。
  小人可以抽象成一个点,出生点坐标是(s, n),保证在第n层云朵上。每次操作,在不越出云朵边界的前提下,你可以控制小人向左移动1单位距离,耗费时间是p;也可以控制小人向右移动1单位距离,耗费时间是q。如果小人处在云朵的边缘(即坐标与线段端点重合),你可以控制它向下跳,那么它将一直坠落到最近的一层云朵或者地面(如果下面没有云朵)上,坠落耗费的时间等于下落的高度。特别地,如果某次下落的高度大于h,小人就会死掉,游戏失败。当小人成功落到地面(即x轴)上时,你就顺利通关了。
  这一次,小H尝试了无数次仍无法在时限内通过某关。你能帮助他吗?

输入格式

  第一行,五个整数n,s,h,p,q。
  接下来n行,每行两个整数li和ri,依次表示每层云朵的左右端点的横坐标。

输出格式

  输出一个整数,表示小人降落到地面最少花费的时间。
  如果不可能成功降落到地面,输出-1。

样例输入

4 0 2 1 1
-2 1
-1 2
-3 0
-2 1

样例输出

6

样例说明

  如上图。出生在S点,先向右移动1单位,向下跳到第2层云朵上,再向右移动1单位,再向下跳,就降落到地面上了。这也是耗时最少的方案。

数据规模和约定

  40%的数据中,1 ≤n≤ 100。
  100%的数据中,1 ≤n, h≤10^5,1 ≤p, q ≤10,-10^5≤li≤ri≤10^5,ln≤s≤rn。

解答

fli,fri,fP 分别为第 i 层左端和右端和点P到地面的最短时间,则很容易发现:

fli=min{ftl+dis(li,tl),ftr+dis(li,tr)}

其中 tl 为第 i 层左端点竖直向下落到的点,tr为第 i 层右端点竖直向下落到的点。当然前提是不会摔死。
在层内走是很容易算出来的,在此就不做叙述。
我们只需要知道如何用i求出 tl,tr 在那一层即可。
于是想到用线段树覆盖,从最底层做起,每上升一层,将这一层覆盖到线段树上,之后只需看 i <script id="MathJax-Element-1119" type="math/tex">i</script>横坐标向下最后覆盖的是那一层即可。
这里线段树可以用map来实现,可以减少打量代码。

参考代码

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <map>
#include <queue>
#include <fstream>

using namespace std;

ifstream cin("jump.in");
ofstream cout("jump.out");

typedef long long ll;
typedef map<int, int>::iterator it;

map<int, int> tree;

int et = -1;
int n, h, s, p, q;
int l[100050], r[100050], ml = 1000000;
ll f[500050][2];

void insert(int i, int l, int r) {
    it st, th;
    st = tree.lower_bound(l);
    th = tree.upper_bound(r);
    int tmp = (--th)->second;
    tree.erase(st, ++th);
    tree[l] = i;
    tree[r] = tmp;
}

int getlastest(int Point) {
    int j = (--tree.upper_bound(Point))->second;
    return j;
}

void readin() {
    cin >> n >> s >> h >> p >> q;
    for (int i = 1; i <= n; i++) {
        cin >> l[i] >> r[i];
        ml = min(ml, l[i]);
    }
}

void work() {
    memset(head, -1, sizeof(head));
    tree[ml] = 0;
    int tmp;
    for (int i = 1; i <= n; i++) {
        f[i][0] = f[i][1] = 1000000000000ll;
        tmp = getlastest(l[i]);
        if (tmp == 0) {
            if (i <= h) {
                f[i][0] = i;
            }
        } else {
            if (i - tmp <= h) {
                f[i][0] = min(f[tmp][0] + (ll)(i - tmp) + (ll)(l[i] - l[tmp]) * (ll)p, f[tmp][1] + (ll)(i - tmp) + (ll)(r[tmp] - l[i]) * (ll)q);
            }
        }
        tmp = getlastest(r[i]);
        if (tmp == 0) {
            if (i <= h) {
                f[i][1] = i;
            }
        } else {
            if (i - tmp <= h) {
                f[i][1] = min(f[tmp][0] + (ll)(i - tmp) + (ll)(r[i] - l[tmp]) * (ll)p, f[tmp][1] + (ll)(i - tmp) + (ll)(r[tmp] - r[i]) * (ll)q);
            }
        }
        insert(i, l[i], r[i]+1);
    }
    ll ans = min(f[n][0] + (ll)(s - l[n]) * (ll)p, f[n][1] + (ll)(r[n] - s) * (ll)q);
    if (ans >= 1000000000000ll) {
        cout << "-1";
    } else {
        cout << ans;
    }
}

int main() {
    ios::sync_with_stdio(false);
    readin();
    work();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值