(倍增)开车旅行

https://www.luogu.org/problemnew/show/P1081
小 A 和小 B 决定利用假期外出旅行,他们将想去的城市从 1到 N编号,且编号较小的城市在编号较大的城市的西边,已知各个城市的海拔高度互不相同,记城市 i 的海拔高度为恰好是这两个城市海拔高度之差的绝对值,即d(i, j) = abs(h[i] - h[j])
旅行过程中,小 A 和小 B轮流开车,第一天小 A 开车,之后每天轮换一次。他们计划选择一个城市 S作为起点,一直向东行驶,并且最多行驶 X 公里就结束旅行。小 A 和小 B的驾驶风格不同,小 B 总是沿着前进方向选择一个最近的城市作为目的地,而小A总是沿着前进方向选择第二近的城市作为目的地(注意:本题中如果当前城市到两个城市的距离相同,则认为离海拔低的那个城市更近)。如果其中任何一人无法按照自己的原则选择目的城市,或者到达目的地会使行驶的总距离超出 X公里,他们就会结束旅行。
在启程之前,小A想知道两个问题:
对于一个给定的 X=X0,从哪一个城市出发,小 A 开车行驶的路程总数与小 B 行驶的路程总数的比值最小(如果小 B 的行驶路程为 0,此时的比值可视为无穷大,且两个无穷大视为相等)。如果从多个城市出发,小 A 开车行驶的路程总数与小B行驶的路程总数的比值都最小,则输出海拔最高的那个城市。
对任意给定的 X=Xi和出发城市 Si ,小 A 开车行驶的路程总数以及小 B 行驶的路程总数。

在每个城市,小a开车会到第二近的城市,小b开车则到最近的城市,如果无法执行自己的要求或者要走的距离大于限制则会停止。在每个城市我们可以预处理出来它能到达的最近和第二近的城市,再使用倍增循环出每个点出发后开车的距离和终点

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
#define inf 2147483645
#define eps 0.000003
int n, h[maxn], s, x, m;
int des[maxn][2], Min[maxn][2];
int to[maxn][22], dis[maxn][22][2];
int ans[2];
struct node {
    int h, id;
    node() {}
    node(int _h, int _id) : h(_h), id(_id) {}
    bool operator <(const node &a) const {
        return h < a.h;
    }
};
set<node> mp;
set<node>:: iterator it;
//des[0]最近的点位置,des[1]第二近的点位置
//Min[x][0]以x为起点的最小海拔差,Min[x][1]---第二小海拔差,方便更新
void up(int u, node x) {
    int v = x.id;
    if((abs(h[u] - h[v]) < Min[u][0]) || (Min[u][0] == abs(h[u] - h[v]) && h[v] < h[des[u][0]])) { //更新最近点
        if((Min[u][0] < Min[u][1]) || (Min[u][1] == Min[u][0] && h[des[u][0]] < h[des[u][1]])) //原来的最近点变成了第二近点
            Min[u][1] = Min[u][0], des[u][1] = des[u][0];
        Min[u][0] = abs(h[u] - h[v]), des[u][0] = v;
    }
    else if((abs(h[u] - h[v]) < Min[u][1]) || (Min[u][1] == abs(h[u] - h[v]) && h[v] < h[des[u][0]])) //注意可能能更新第二近点
        Min[u][1] = abs(h[u] - h[v]), des[u][1] = v;
}
void judge(int xi, int si) { //判断以xi为起点,si的距离限制a,b走多远
    for(int k = 20; k >= 0; k--) {
        if(dis[xi][k][0] + dis[xi][k][1] <= si && to[xi][k]) {
            si -= (dis[xi][k][0] + dis[xi][k][1]);
            ans[1] += dis[xi][k][1];
            ans[0] += dis[xi][k][0];
            xi = to[xi][k];
        }
    }
    if(des[xi][1] && Min[xi][1] <= si) ans[1] += Min[xi][1]; //a,b分别开一次不满足,可能a还能开一次
}
int main()
{
    scanf("%d", &n);
    register int i, j, k;
    for(i = 1; i <= n; i++) scanf("%d", &h[i]), Min[i][0] = Min[i][1] = inf;
    //预处理出每个点的最近两点
    for(i = n; i >= 1; i--) {
        mp.insert(node(h[i], i));
        it = mp.find(node(h[i], i));
        it++;
        if(it != mp.end()) {
            up(i, *it);
            it++;
            if(it != mp.end()) up(i, *it);
            it--;
        }
        it--;
        if(it != mp.begin()) {
            up(i, *(--it));
            if(it != mp.begin()) up(i, *(--it));
        }
    }
    //to记录每一轮ab两人轮换开车后的目的地
    //dis记录i起点,最近,第二近能到达的点的情况
    for(i = 1; i <= n; i++) {
        to[i][0] = des[des[i][1]][0];
        dis[i][0][1] = Min[i][1];
        dis[i][0][0] = Min[des[i][1]][0];
    }
    for(k = 1; k <= 20; k++) {
        for(i = 1; i <= n; i++) {
            to[i][k] = to[to[i][k - 1]][k - 1];
            dis[i][k][1] = dis[i][k - 1][1] + dis[to[i][k - 1]][k - 1][1];
            dis[i][k][0] = dis[i][k - 1][0] + dis[to[i][k - 1]][k - 1][0];
        }
    }
    scanf("%d", &x);
    double rate = inf;
    int pos = 0;
    h[0] = -inf;
    for(i = 1; i <= n; i++) {
        ans[0] = ans[1] = 0;
        judge(i, x);
        double tmp = ans[0] ? 1.0 * ans[1] / ans[0] : inf;
        if(tmp - rate < eps && tmp - rate > -eps && h[i] > h[pos]) pos = i;
        if(rate - tmp > eps) pos = i, rate = tmp;
    }
    printf("%d\n", pos);
    scanf("%d", &m);
    while(m--) {
        scanf("%d%d", &s, &x);
        ans[0] = ans[1] = 0;
        judge(s, x);
        printf("%d %d\n", ans[1], ans[0]);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值