[luogu p1081] 开车旅行

\(\mathtt{Link}\)

传送门

\(\mathtt{Description}\)

此题为复杂细节题,无法总结题意,所以给出原题:

\(\text{A}\) 和小 \(\text{B}\) 决定利用假期外出旅行,他们将想去的城市从 $1 $ 到 \(n\) 编号,且编号较小的城市在编号较大的城市的西边,已知各个城市的海拔高度互不相同,记城市 \(i\) 的海拔高度为\(h_i\),城市 \(i\) 和城市 \(j\) 之间的距离 \(d_{i,j}\) 恰好是这两个城市海拔高度之差的绝对值,即 \(d_{i,j}=|h_i-h_j|\)

旅行过程中,小 \(\text{A}\) 和小 \(\text{B}\) 轮流开车,第一天小 \(\text{A}\) 开车,之后每天轮换一次。他们计划选择一个城市 \(s\) 作为起点,一直向东行驶,并且最多行驶 \(x\) 公里就结束旅行。

\(\text{A}\) 和小 \(\text{B}\) 的驾驶风格不同,小 \(\text{B}\) 总是沿着前进方向选择一个最近的城市作为目的地,而小 \(\text{A}\) 总是沿着前进方向选择第二近的城市作为目的地(注意:本题中如果当前城市到两个城市的距离相同,则认为离海拔低的那个城市更近)。如果其中任何一人无法按照自己的原则选择目的城市,或者到达目的地会使行驶的总距离超出 \(x\) 公里,他们就会结束旅行。

在启程之前,小 \(\text{A}\) 想知道两个问题:

1、 对于一个给定的 \(x=x_0\),从哪一个城市出发,小 \(\text{A}\) 开车行驶的路程总数与小 \(\text{B}\) 行驶的路程总数的比值最小(如果小 \(\text{B}\) 的行驶路程为 \(0\),此时的比值可视为无穷大,且两个无穷大视为相等)。如果从多个城市出发,小 \(\text{A}\) 开车行驶的路程总数与小 \(\text{B}\) 行驶的路程总数的比值都最小,则输出海拔最高的那个城市。

2、对任意给定的 \(x=x_i\) 和出发城市 \(s_i\),小 \(\text{A}\) 开车行驶的路程总数以及小 \(\text B\) 行驶的路程总数。

\(\mathtt{Solution}\)

暴力

首先考虑暴力做法。

预处理出四个数组:A, B, disA, disB。

  • \(A_i\) 表示车在第 \(i\) 个城市时,小A选择的目的地;
  • \(B_i\) 表示车在第 \(i\) 个城市时,小B选择的目的地;
  • \(disA_i\) 表示车从 \(i\) 到达小A选择目的地(即 \(A_i\))的距离;
  • \(disB_i\) 表示车从 \(i\) 到达小B选择目的地(即 \(B_i\))的距离。

然后模拟行车过程即可。

代码就不给了,懒得写……

\(\mathtt{Time} \text{ } \mathtt{Complexity}\)

稍加思考便可知,预处理是 \(\mathcal{O}(n ^ 2)\) 的。

然后再模拟行车过程,复杂度 \(\mathcal{O}(nm)\)

观察数据范围,暴力做法能得到 \(70 \mathtt{pts}\),分数非常可观。

倍增

接下来就是正菜了,我们考虑在暴力的基础上优化一下。

观察到每次A和B的目标点在位置确定时就已经确定了,因此考虑倍增

distotA[i][j] 表示汽车从i,被A和B轮流分别开了 1 << j 次后 A 的总路程
distotB[i][j] 表示汽车从i,被A和B轮流分别开了 1 << j 次后 B 的总路程
curpos[i][j] 表示汽车从i,被A和B轮流分别开了 1 << j 次后汽车的位置

先考虑 \(j=0\) 的情况:

    for (int i = 1; i <= n; ++i) {
        distotA[i][0] = disA[i];
        distotB[i][0] = disB[A[i]];
        curpos[i][0] = B[A[i]];
    }

这个还是很好理解的吧!

再来看看 \(j \ge 1\) 的情况:

    for (int j = 1; j <= maxlogn - 5; ++j)
        for (int i = 1; i <= n; ++i) {
            curpos[i][j] = curpos[curpos[i][j - 1]][j - 1];
            if (curpos[i][j]) {
                distotA[i][j] = distotA[i][j - 1] + distotA[curpos[i][j - 1]][j - 1];
                distotB[i][j] = distotB[i][j - 1] + distotB[curpos[i][j - 1]][j - 1];
            }
        }

那么倍增我们就优化完了,一个 \(n\) 掉成了 \(\log n\)。但是这还远远不够。

disA和disB的处理仍然是平方级别的。怎么办呢?我们使用双向链表来把这里的预处理降低成 \(\mathcal{O}(n)\) 级别。

首先,我们把这 \(n\) 个城市按照高度排序。(好吧,这已经 \(n \log n\) 了)

排序后,那么一个城市 \(i\) 的最小距离点和次小距离点,一定就在 \(i-2\), \(i-1\), \(i+1\), \(i+2\) 这四个位置上

那么第一个找点的显然就是一号城市。然后你就会惊奇的发现,一号城市就是最西边了,也就是说找到的任何城市都在一号城市东边。这大大方便我们直接计算disA和disB的值。

找到之后,炸了一号城市,开始处理二号城市。嗯因为一号城市已经被炸了,同样所有城市都在二号城市东边,太方便啦!

处理完一个炸一个,直到最后炸没了,整个disA和disB就求解完毕了。

至于为什么用双向链表嘛,这是因为炸城市需要链表维护,然后我们又需要找一个城市的前驱和后继,嗯,妥妥双向链表。

\(\mathtt{Time} \text{ } \mathtt{Complexity}\)

  • 排序 \(\mathcal{O}(n \log n)\)
  • 双向链表处理disA和disB \(\mathcal{O}(n)\)
  • 倍增处理distotA和distotB \(\mathcal{O}(n\log n)\)
  • 解决第一小问 \(\mathcal{O}(n\log n)\)
  • 解决第二小问 \(\mathcal{O}(m\log n)\)

整体就是 \(\mathcal{O}(n\log n)\) 这个级别的,显然这个算法可以通过此题。

那就上代码吧!

\(\mathtt{Code}\)

/*
 * @Author: crab-in-the-northeast 
 * @Date: 2020-11-20 23:36:45 
 * @Last Modified by: crab-in-the-northeast
 * @Last Modified time: 2020-11-21 01:00:02
 */
#include <bits/stdc++.h>

const int maxn = 100005;
const int maxlogn = 25;
const double eps = 0.0000001;

inline long long read() {
    long long x = 0;
    bool f = true;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            f = false;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 1) + (x << 3) + ch - '0';
        ch = getchar();
    }
    if (f) 
        return x;
    return ~(x - 1);
}

int n;
long long A[maxn], B[maxn], disA[maxn], disB[maxn], distotA[maxn][maxlogn], distotB[maxn][maxlogn], curpos[maxn][maxlogn];

struct building {
    long long h;
    int num, lst, nxt;
    const bool operator < (const building& b) {
        return this -> h < b.h;
    }
}a[maxn];

int p[maxn];

inline void upddis(int spos, int s, int t) {
    if (t < 1 || t > n)
        return ;
    int dis = std :: abs(a[s].h - a[t].h);
    if (disB[spos] == 0 || disB[spos] > dis || (disB[spos] == dis && a[t] < a[p[B[spos]]])) {
        disA[spos] = disB[spos];
        disB[spos] = dis;
        A[spos] = B[spos];
        B[spos] = a[t].num;
    } else if (disA[spos] == 0 || disA[spos] > dis || (disA[spos] == dis && a[t] < a[p[A[spos]]])) {
        disA[spos] = dis;
        A[spos] = a[t].num;
    }
    return ;
}

int main() {
    n = read();
    for (int i = 1; i <= n; ++i) {
        a[i].num = i;
        a[i].h = read();
    }
    std :: sort(a + 1, a + 1 + n);
    for (int i = 1; i <= n; ++i) {
        if (i != 1)
            a[i].lst = i - 1;
        if (i != n)
            a[i].nxt = i + 1;
        p[a[i].num] = i;
    }
    
    // solve disA, disB On
    for (int i = 1; i <= n; ++i) {
        int pos = p[i];
        upddis(i, pos, a[a[pos].lst].lst);
        upddis(i, pos, a[pos].lst);
        upddis(i, pos, a[pos].nxt);
        upddis(i, pos, a[a[pos].nxt].nxt);
        if (a[pos].lst)
            a[a[pos].lst].nxt = a[pos].nxt;
        if (a[pos].nxt)
            a[a[pos].nxt].lst = a[pos].lst;
        a[pos].lst = a[pos].nxt = 0;
    }

    //puts("fff");
    // solve distotA, distotB, Onlogn
    for (int i = 1; i <= n; ++i) {
        distotA[i][0] = disA[i];
        distotB[i][0] = disB[A[i]];
        curpos[i][0] = B[A[i]];
    }

    for (int j = 1; j <= maxlogn - 5; ++j)
        for (int i = 1; i <= n; ++i) {
            curpos[i][j] = curpos[curpos[i][j - 1]][j - 1];
            if (curpos[i][j]) {
                distotA[i][j] = distotA[i][j - 1] + distotA[curpos[i][j - 1]][j - 1];
                distotB[i][j] = distotB[i][j - 1] + distotB[curpos[i][j - 1]][j - 1];
            }
        }
    
    // solve Part1 nlogn
    long long x0 = read(), ans = 0;
    double minrat = INT_MAX;
    
    for (int i = 1; i <= n; ++i) {
        long long ansA = 0, ansB = 0, pos = i, tmpx = x0;
        for (int j = maxlogn - 5; j >= 0; --j) {
            if (distotA[pos][j] + distotB[pos][j] && tmpx >= distotA[pos][j] + distotB[pos][j]) {
                tmpx -= distotA[pos][j] + distotB[pos][j];
                ansA += distotA[pos][j];
                ansB += distotB[pos][j];
                pos = curpos[pos][j];
            }
        }
        if (disA[pos] <= tmpx)
            ansA += disA[pos];
        if (ansA == 0)
            continue;
        if (ans == 0 || minrat - 1.0 * ansA / ansB > eps || (fabs(minrat - 1.0 * ansA / ansB) <= eps && a[p[ans]] < a[p[i]])) {
            minrat = 1.0 * ansA / ansB;
            ans = i;
        }
    }
    std :: printf("%lld\n", ans);

    // solve Part2 mlogn
    int m = read();
    while (m--) {
        long long s = read(), x = read(), ansA = 0, ansB = 0;
        for (int j = maxlogn - 5; j >= 0; --j) {
            if (distotA[s][j] + distotB[s][j] && x >= distotA[s][j] + distotB[s][j]) {
                x -= distotA[s][j] + distotB[s][j];
                ansA += distotA[s][j];
                ansB += distotB[s][j];
                s = curpos[s][j];
            }
        }
        if (disA[s] <= x) 
            ansA += disA[s];
        std :: printf("%lld %lld\n", ansA, ansB);
    }

    return 0;
}


\(\mathtt{More}\)

如果对于一个点,选择具有唯一性(或者说跳到哪里的选择只和位置有关),那么就可以考虑倍增优化

附一个白话倍增(经 典 作 品)

posted @ 2020-11-21 23:50  东北小蟹蟹  阅读(78)  评论(0编辑  收藏