【NOIP 2012】开车旅行 倍增+set

题目描述 Description
小A 和小B决定利用假期外出旅行,他们将想去的城市从1到N 编号,且编号较小的城市在编号较大的城市的西边,已知各个城市的海拔高度互不相同,记城市 i的海拔高度为Hi,城市 i 和城市 j 之间的距离 d[i,j]恰好是这两个城市海拔高度之差的绝对值,即d[i, j] = |Hi − Hj|。

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

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

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

2.对任意给定的 X=Xi和出发城市 Si,小 A 开车行驶的路程总数以及小 B 行驶的路程总数。

输入描述 Input Description

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

第二行有 N 个整数,每两个整数之间用一个空格隔开,依次表示城市 1 到城市 N 的海拔高度,即H1,H2,……,Hn,且每个Hi都是不同的。

第三行包含一个整数 X0。

第四行为一个整数 M,表示给定M组Si和 Xi。

接下来的M行,每行包含2个整数Si和Xi,表示从城市 Si出发,最多行驶Xi公里。
输出描述 Output Description
输出共M+1 行。

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

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

样例输入 Sample Input

【样例1】

4

2 3 1 4

3

4

1 3

2 3

3 3

4 3

【样例2】

10

4


 5 6 1 2 3 7 8 9 10

7

10

1 7

2 7

3 7

4 7

5 7

6 7

7 7

8 7

9 7

10 7

样例输出 Sample Output

【样例1】

1

1 1

2 0

0 0

0 0

【样例2】

2

3 2

2 4

2 1

2 4

5 1

5 1

2 1

2 0

0 0

0 0

数据范围及提示 Data Size & Hint

【输入输出样例1说明】

各个城市的海拔高度以及两个城市间的距离如上图所示。

如果从城市1出发, 可以到达的城市为2,3,4,这几个城市与城市 1的距离分别为 1,1,2,但是由于城市3的海拔高度低于城市 2,所以我们认为城市 3离城市 1最近,城市 2离城市1 第二近,所以小 A 会走到城市 2。到达城市 2 后,前面可以到达的城市为 3,4,这两个城市与城市 2 的距离分别为 2,1,所以城市 4离城市 2最近,因此小 B 会走到城市 4。到达城市4后,前面已没有可到达的城市,所以旅行结束。

如果从城市2出发,可以到达的城市为3,4,这两个城市与城市 2 的距离分别为 2,1,由于城市3离城市2第二近,所以小A会走到城市 3。到达城市3后,前面尚未旅行的城市为4,所以城市 4 离城市 3 最近,但是如果要到达城市 4,则总路程为 2+3=5>3,所以小 B 会直接在城市3结束旅行。

如果从城市3出发,可以到达的城市为4,由于没有离城市3 第二近的城市,因此旅行还未开始就结束了。

如果从城市4出发,没有可以到达的城市,因此旅行还未开始就结束了。

【输入输出样例2说明】

当 X=7时,

如果从城市1出发,则路线为 1 -> 2 -> 3 -> 8 -> 9,小A 走的距离为1+2=3,小B走的距离为 1+1=2。(在城市 1 时,距离小 A 最近的城市是 2 和 6,但是城市 2 的海拔更高,视为与城市1第二近的城市,所以小A 最终选择城市 2;走到9后,小A只有城市10 可以走,没有第2选择可以选,所以没法做出选择,结束旅行)

如果从城市2出发,则路线为 2 -> 6 -> 7 ,小A 和小B走的距离分别为 2,4。

如果从城市3出发,则路线为 3 -> 8 -> 9,小A和小B走的距离分别为 2,1。

如果从城市4出发,则路线为 4 -> 6 -> 7,小A和小B走的距离分别为 2,4。

如果从城市5出发,则路线为 5 -> 7 -> 8 ,小A 和小B走的距离分别为 5,1。

如果从城市6出发,则路线为 6 -> 8 -> 9,小A和小B走的距离分别为 5,1。

如果从城市7出发,则路线为 7 -> 9 -> 10,小A 和小B走的距离分别为 2,1。

如果从城市8出发,则路线为 8 -> 10,小A 和小B走的距离分别为2,0。

如果从城市 9 出发,则路线为 9,小 A 和小 B 走的距离分别为 0,0(旅行一开始就结束了)。

如果从城市10出发,则路线为 10,小A 和小B 走的距离分别为0,0。

从城市 2 或者城市 4 出发小 A 行驶的路程总数与小 B 行驶的路程总数的比值都最小,但是城市2的海拔更高,所以输出第一行为2。

【数据范围】

对于30%的数据,有1N201≤M≤20;

对于40%的数据,有1N1001≤M≤100;

对于50%的数据,有1N1001≤M≤1,000;

对于70%的数据,有1N1,0001≤M≤10,000;

对于100%的数据,有1N100,0001≤M≤10,000, -1,000,000,000≤Hi≤1,000,000,0000≤X0≤1,000,000,0001≤Si≤N0≤Xi≤1,000,000,000,数据保证Hi互不相同。

题解:
这个题目初次看上去有很多难点,甚至于模拟都不好写。写模拟的难点在于第一每个点往后面A开车和B开车到达的点不好计算。
暴力建图方法:时间复杂度O(N^2)
因为每一个点不论A、B都只会往编号大的点走也就是当前点右边的点走。那么我们自然而然的想到,对于每一个点我们暴力的统计当前点右边点的高度,然后计算出A会到达的点B会到达的点,时间复杂度O(N^2)。
优化建图方法:时间复杂度O(NlogN)
上面的那种建图方法的缺点在于对于任何一点它右边点对于当前点的最近和次近都是重新统计,而没有利用之前的已有信息。那么我们想一下我们应该怎么把前面的数据利用起来呢?很容易就可以想到倘若是没到一个点我们就将该点的高度插入一颗平衡树中,然后我们在这个平衡树中找这个点的左右子树并借此来找出A会到达的点B会到达的点。而这种基本操作的平衡树我们可以用STL中的set实现。这样的话我们每次查找插入操作的时间复杂度是O(logN),而一共要插入N个点,建图的时间复杂度就是O(NlogN)。
在我们将建图方法优化以后建图的时间复杂度在100分的数据上面就不会超时。
暴力解决问题:时间复杂度O(N*M)
这个很简单的直接跑就可以了。
优化解决问题:时间复杂度O(MlogN)
首先我们这个题目的建图没有成为限制程序快慢的因素,那么我们就应该在解决问题方面进行优化的方面。对于这个题目我们还未看过上面建出来的图是什么样子。只有当我们将图形画出来了才能选择相对应的算法去解决这个问题。
【样例1】

4

2 3 1 4

A从每个点出发到去的每个点连一条边,得到是左图。
B 从每个点出发到去的每个点连一条边,得到是右图。
这里写图片描述
我们可以看到这个图在我旋转之后摆放呈现出了树的型状。内心一下子激动起来。我们先还是应该淡定,想想呈现出“树”究竟是机缘巧合还是命中注定?
我们每个点不论是A开车还是B开车(除去不存在的点)那么我们每个点一定会连一条边去另外一个点,同时存在一条或者多条边连到当前点。这刚好符合树的定义:每一个非根节点有且只有一个父节点;我们已经压抑不住内心的激动了,不论是A到的点还是B到的点当我们把当前点和将要到达的点之间用线连接,那么我们必定会得到一颗树,我们在树上的话我们就可以用倍增算法帮我们更快的遍历整棵树。
现在我们的大方向基本上是定下来了,但是有个问题——我们这儿有AB两个人分别开车一个开一天的车,对应的树就有两棵这样很明显我们的问题还是不能得到解决。我们怎么倍增呢?
因为每次都是A开车B开车,周而复始直到最终结束。那么我们就A走一步,B走一步并且把这一轮两天当做倍增中的一步。同时我们将树也用这种方法揉在一起。
这样的话我们就可以以O(logN)走到这条路的尽头,当然一定会有一个问题?倘若是最后一步是A走的怎么办?很简单,因为题目条件限制了,当我们最后将要走的路程超过了X就不走了。这样的话,我们先用倍增跑到不能跑为止,然后我们在判断最后一次A+B跑一轮距离远了,那我们只有A跑的话距离会不会远呢?单独加一句话判断。
这样的话第一个问题,我们就直接枚举每一个起点,然后分别计算出A和B走过的路直到算出比值最小的,这儿要注意。我们要把海拔高度排序以后再枚举,因为海拔高的优先级高,我们把海拔高度排序以后处理更方便。因为有N个点,每个点用去logN的时间复杂度,总时间复杂度O(NlogN)
第二个问题,同上,一共M次询问,每次询问O(logN)的时间复杂度遍历重时间复杂度O(MlogN)

所以最终解决这个问题的时间复杂度就是O(NlogN)。PS:因为M和N是一个量级我就直接写作O(NlogN)了。

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <set>
const long long MAXN=100005,MAXD=20;
using namespace std;

long long n,c,xo,ans=1000,distA,distB,st,a=1000000005,b,t;
long long next[MAXN][2],dist[MAXN][2],sum[MAXN][MAXD+5][2],anc[MAXN][MAXD+5];

inline long long abs(long long a,long long b) {return a>0 ? a:-a ;}

struct node
{
    friend bool operator < (const node &a,const node &b)
    {
        return a.c<b.c;
    }
    long long num;
    long long c;
}City[MAXN];
set<node>s;
set<node>::iterator it;
inline void Update(node a,node b)
{
   if(!next[a.num][0]){  
    next[a.num][0] = b.num;dist[a.num][0] = abs(a.c - b.c);
   }
   else if(dist[a.num][0]>abs(a.c-b.c)||dist[a.num][0]==abs(a.c-b.c)&&City[ next[a.num][0] ].c>b.c)  {
    next[a.num][1] = next[a.num][0];dist[a.num][1] = dist[a.num][0];
    next[a.num][0] = b.num; dist[a.num][0]=abs(a.c-b.c);
   }
   else if(dist[a.num][1]>abs(a.c-b.c)||dist[a.num][1]==0){
    next[a.num][1] = b.num; dist[a.num][1] = abs(a.c - b.c);
   }
   else if(dist[a.num][1]==abs(a.c-b.c)&&City[ next[a.num][1] ].c>b.c){
    next[a.num][1] = b.num; dist[a.num][1] = abs(a.c - b.c);
   }
}
void init()
{
    scanf("%d",&n);
    for(long long i=1;i<=n;i++){
        scanf("%lld",&c);
        City[i].c=c;
        City[i].num=i;
    }

    for(long long i=n;i>=1;i--){
        s.insert(City[i]);
        it=s.find(City[i]);

        if(it != s.begin()){
            it--;Update(City[i],*it);
            if(it != s.begin()) {it--;Update(City[i],*it);it++;}
            it++;   
        }

        if((++it) != s.end() ){
          Update(City[i],*it);
            if((++it) != s.end()) {Update(City[i],*it);it--;}
            it--;
        }
    }
}
inline void query(long long s,long long x)  //s是起点 x是路程 
{
    distA=distB=0;
    for(long long i=MAXD;i>=0;i--)
     if(x>=sum[s][i][0]+sum[s][i][1]&&anc[s][i]){
        distA+=sum[s][i][0];
        distB+=sum[s][i][1];
        x-=(sum[s][i][0]+sum[s][i][1]);
        s=anc[s][i];
    }
    if(next[s][1]&&dist[s][1]<=x) distA+=dist[s][1];
}
int main()
{
    init();
    for(int i=1;i<=n;i++){
     anc[i][0]=next[ next[i][1] ][0];
     sum[i][0][0] = dist[i][1];  //小A走过的路 
     sum[i][0][1] = dist[next[i][1]][0];  //小B走过的路 
    }
    for(int j=1;j<=MAXD;j++){
        for(int i=1;i<=n;i++){
        anc[i][j] = anc[ anc[i][j-1] ][j-1];
        sum[i][j][0] = sum[ anc[i][j-1] ][j-1][0]+sum[i][j-1][0];//小A走过的路
        sum[i][j][1] = sum[ anc[i][j-1] ][j-1][1]+sum[i][j-1][1];//小B走过的路
        }   
    }
    scanf("%lld",&xo);
    for(it=s.begin();it!=s.end();it++)
    {
        query((*it).num,xo);
        if(distB&&(!st||distB*a>=distA*b)){
            a=distA;
            b=distB;
            st=(*it).num;
        }
    }

   printf("%lld\n",st);
   scanf("%lld",&t);
   for(int i=1;i<=t;i++){
     scanf("%lld%lld",&st,&xo);
     query(st,xo);
     printf("%lld  %lld\n",distA,distB);
   }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值