• [luogu p1081] 开车旅行


    (mathtt{Link})

    传送门

    (mathtt{Description})

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

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

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

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

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

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

    2、对任意给定的 (x=x_i) 和出发城市 (s_i),小 ( ext{A}) 开车行驶的路程总数以及小 ( ext 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} ext{ } 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} ext{ } mathtt{Complexity})

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

    整体就是 (mathcal{O}(nlog 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
    ", 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
    ", ansA, ansB);
        }
    
        return 0;
    }
    
    
    

    (mathtt{More})

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

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

  • 相关阅读:
    Java 利用SWFUpload多文件上传 session 为空失效,不能验证的问题 swfUpload多文件上传
    对ExtJS4应用 性能优化的几点建议
    Extjs4中用combox做下拉带图片的下拉框
    当你的才华还撑不起你的野心时,就应该静下心来学习(转)
    占位符行为 PlaceHolderBehavior 的实现以及使用
    一个简单的TabItem样式。
    WPF实现Twitter按钮效果(转)
    模仿36。杀毒~button(转)
    WPF自适应可关闭的TabControl 类似浏览器的标签页(转)
    WPF绘制简单常用的Path(转)
  • 原文地址:https://www.cnblogs.com/crab-in-the-northeast/p/luogu-p1081.html
Copyright © 2020-2023  润新知