【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;
}

转载于:https://www.cnblogs.com/2020pengxiyue/p/9536367.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值