1. 程式人生 > >(倍增)開車旅行

(倍增)開車旅行

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]);
    }
}