• 【NOIP2012】开车旅行


    题目描述

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

    旅行过程中,小 \(A\) 和小 \(B\) 轮流开车,第一天小 \(A\) 开车,之后每天轮换一次。他们计划选择一个城市 \(S\) 作为起点,一直向东行驶,并且最多行驶 \(X\) 公里就结束旅行。小 \(A\) 和小 \(B\) 的驾驶风格不同,小 \(B\) 总是沿着前进方向选择一个最近的城市作为目的地,而小\(A\)总是沿着前进方向选择第二近的城市作为目的地(注意:本题中如果当前城市到两个城市的距离相同,则认为离海拔低的那个城市更近)。如果其中任何一人无法按照自己的原则选择目的城市,或者到达目的地会使行驶的总距离超出 \(X\) 公里,他们就会结束旅行。

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

    对于一个给定的 \(X=X_0\),从哪一个城市出发,小 \(A\) 开车行驶的路程总数与小 \(B\) 行驶的路程总数的比值最小(如果小 \(B\) 的行驶路程为 \(0\) ,此时的比值可视为无穷大,且两个无穷大视为相等)。如果从多个城市出发,小 \(A\) 开车行驶的路程总数与小 \(B\) 行驶的路程总数的比值都最小,则输出海拔最高的那个城市。
    对任意给定的 \(X=X_i\) 和出发城市 \(S_i\) ,小 \(A\) 开车行驶的路程总数以及小 \(B\) 行驶的路程总数。

    输入输出格式

    输入格式

    第一行包含一个整数 \(N\) ,表示城市的数目。

    第二行有 \(N\) 个整数,每两个整数之间用一个空格隔开,依次表示城市 \(1\) 到城市 \(N\) 的海拔高度,即 \(H_1\),\(H_2\),\(…\),\(H_n\) ,且每个 \(H_i\) 都是不同的。

    第三行包含一个整数 \(X_0\)

    第四行为一个整数 \(M\) ,表示给定 \(M\)\(S_i\)\(X_i\)

    接下来的 \(M\) 行,每行包含 \(2\) 个整数 \(S_i\)\(X_i\), 表示从城市 \(S_i\) 出发,最多行驶 \(X_i\) 公里。

    输出格式

    输出共 \(M+1\) 行。

    第一行包含一个整数 \(S_0\),表示对于给定的 \(X_0\),从编号为 \(S_0\) 的城市出发,小 \(A\) 开车行驶的路程总数与小 \(B\) 行驶的路程总数的比值最小。

    接下来的 \(M\) 行,每行包含 \(2\) 个整数,之间用一个空格隔开,依次表示在给定的 \(S_i\)\(X_i\) 下小 \(A\) 行驶的里程总数和小 \(B\) 行驶的里程总数。

    数据范围与约定

    对于30%的数据,有 \(1≤N≤20\),\(1≤M≤20\) ;
    对于40%的数据,有 \(1≤N≤100\),\(1≤M≤100\)
    对于50%的数据,有 \(1≤N≤100\),\(1≤M≤1,000\)
    对于70%的数据,有 \(1≤N≤1,000\),\(1≤M≤10,000\) ;
    对于100%的数据,有 \(1≤N≤100,000\),\(1≤M≤100,000\), \(-10^9≤H_i≤10^9\) ,数据保证 \(H_i\) 互不相同。

    题解

    我们先来看前70%的数据,我们发现可以用 \(O(n^2)\) 的预处理来算出小\(A\)在某个位置i的下一个位置和小\(B\)在某个位置的下一个位置。然后对于第一个问题我们可以枚举每一个出发点,然后进行比较,对于第二问我们可以直接模拟计算即可。

    我们再来考虑100%的算法,我们可以发现上面这个算法最最耗时的地方一个是在预处理上,一个是在枚举和模拟上。

    我们依次来考虑是否可以优化:
       1.对于 \(O(n^2)\) 的预处理时,我们可以利用数据结构来优化使其降至 \(O(nlogn)\) ,我们需要一个支持插入操作和求区间第K大的数据结构,通常我们会用线段树、双向链、set、平衡树来维护,蒟蒻我对于其他几种不是很了解,只能用set来水了。注意在用set时迭代器是否越界。
      2.对于枚举算法,我么发现我们找不出什么好的优化算法,所以我们只能放弃。
      3.对于模拟,我们发现,是小\(A\)和小\(B\)轮流进行,我们可以考虑用倍增进行优化就像树上倍增求LCA一样,我们把小\(A\)和小\(B\)各走一步看作是一轮,记录\(f[i][j]\)表示从\(i\)出发经过\(2^j\)轮后他们回到达的地方,记录\(fa[i][j]\)表示从\(i\)出发,经过\(2^j\)轮后小\(A\)走过的路程,同理记录小\(B\)的为\(fb[i][j]\)
        由于\(2^{j-1} + 2^{j-1} = 2^j\) ,考虑一下状态的转移:
         \(f[i][j] = f[f[i][j-1]][j-1]\);
         \(fa[i][j] = fa[i][j-1] + fa[f[i][j-1]][j-1]\);
         \(fb[i][j] = fb[i][j-1] + fb[f[i][j-1]][j-1]\);
        值得注意的是:
         1.要考虑初始条件;
         2.在进行倍增的过程中,如果无法两个人都走的时候注意考虑小\(A\)是否可以单独多走一步;
         3.在递推过程中,我们考虑一下状态的产生顺序,发现要以j所在的维度为阶段。

    代码

    #include<bits/stdc++.h>
    using namespace std;
    
    int n, m, S, X;
    long long h[100005];
    int a[100005], b[100005];
    
    struct Node{
        long long val;
        int pos;
        bool friend operator < (const Node & x, const Node & y)
            {
                return x.val < y.val;
            }
    };
    
    inline Node make_Node(int x, int y)
        {
            Node aa;
            aa.val = x, aa.pos = y;
            return aa;
        }
    
    set <Node> Set;
    Node dis[5];
    inline bool comp(const Node & x, const Node & y)
    	{
    		if(abs(x.val) == abs(y.val))	return x.val < y.val;
            return abs(x.val) < abs(y.val);
    	}
    void init1()
        {
            set <Node> :: iterator it;
            for(int i = n; i > 0; -- i)
                {
                    memset(dis, 0, sizeof(dis));
                    int cnt = 0;
                    Set.insert(make_Node(h[i], i));
                    it = Set.find(make_Node(h[i], i));
                    if(it != Set.begin())
                        {
                            -- it, dis[++ cnt] = *it;
                            if(it != Set.begin())	--it, dis[++ cnt] = *it, ++ it;
                            ++ it;
                        }
                    ++ it;
                    if(it != Set.end())
                        {
                            dis[++ cnt] = *it, ++ it;
                            if(it != Set.end()) dis[++ cnt] = *it;
                        }
                    for(int j = 1; j <= cnt; ++ j)	dis[j].val -= h[i];
                    sort(dis + 1, dis + cnt + 1, comp);
                    a[i] = dis[2].pos, b[i] = dis[1].pos;
                }
            return;
        }
    
    int f[100005][20];	
    long long fa[100005][20], fb[100005][20];
    int t;
    void init2()
        {
            t = (int) log(n) / log(2) + 1;
            for(int i = 1; i <= n; ++ i)
                {
                    int posa = a[i], posb = b[a[i]];
                    f[i][0] = posb, fa[i][0] = posa ? abs(h[i] - h[posa]) : 0, fb[i][0] = posb ? abs(h[posa] - h[posb]) : 0;	
                }
            for(int j = 1; j <= t; ++ j)
            	for(int i = 1; i <= n; ++ i)
                        f[i][j] = f[f[i][j - 1]][j - 1], fa[i][j] = fa[i][j - 1] + fa[f[i][j - 1]][j - 1], fb[i][j] = fb[i][j - 1] + fb[f[i][j - 1]][j - 1];
        }
        
    void Get_ans(int s, int x0, long long & suma, long long &sumb)
        {
            suma = sumb = 0;
            for(int i = t; i >= 0; -- i)
                if(f[s][i] && fa[s][i] + fb[s][i] <= x0)
                    {
                        suma += fa[s][i], sumb += fb[s][i], x0 -= (fa[s][i] + fb[s][i]);
                        s = f[s][i];
                    }
            int posa = a[s];
            if(!posa)	return;
            if(abs(h[posa] - h[s]) <= x0) suma += abs(h[posa] - h[s]);
            return;	
        }
    
    int main()
    {
    //	freopen("drive.in", "r", stdin);
    //	freopen("drive.out", "w", stdout);
        scanf("%d", &n);
        for(int i = 1; i <= n; ++ i) scanf("%lld", &h[i]);
        init1();
    //	for(int i = 1; i <= n; ++ i)	printf("%d %d\n", a[i], b[i]);
        init2();
        scanf("%d", &X);
        long long ansa = 0x7fffffff, ansb = 1;
        long long suma, sumb;
        for(int i = 1; i <= n; ++ i)
            {
                Get_ans(i, X, suma, sumb);
                if(sumb == 0) continue;
                if(ansa * sumb > suma * ansb)	ansa = suma, ansb = sumb, S = i;
            }
        printf("%d\n", S);
        scanf("%d", &m);
        for(int i = 1; i <= m; ++ i)
            {
                scanf("%d%d", &S, &X);
                Get_ans(S, X, suma, sumb);
                printf("%lld %lld\n", suma, sumb);
            }
        return 0;
    }
    
  • 相关阅读:
    图说jdk1.8新特性(3)--- 注解与类型推测优化
    图说jdk1.8新特性(2)--- Lambda
    图说jdk1.8新特性(1)--- 函数式接口
    Linux应用与端口
    Linu如何查看磁盘占用情况及处理办法
    ThreadLocal剧集(一)
    缓存穿透、缓存击穿、缓存雪崩区别和解决方案
    MySQL 新建用户,为用户授权,指定用户访问数据库
    2019面试总结
    Java的设计模式
  • 原文地址:https://www.cnblogs.com/pxyWaterMoon/p/9536367.html
Copyright © 2020-2023  润新知