Noip 2012 开车旅行(倍增思想的活用)

题目描述

小 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 行驶的路程总数。

输入输出格式

输入格式:
第一行包含一个整数 N,表示城市的数目。

第二行有 N 个整数,每两个整数之间用一个空格隔开,依次表示城市 1 到城市 N 的海

拔高度,即 H1,H2,……,Hn,且每个 Hi都是不同的。

第三行包含一个整数 X0。

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

接下来的 M 行,每行包含 2 个整数 Si和 Xi,表示从城市 Si出发,最多行驶 Xi公里。

输出格式:
输出共 M+1 行。

第一行包含一个整数 S0,表示对于给定的 X0,从编号为 S0的城市出发,小 A 开车行驶

的路程总数与小 B 行驶的路程总数的比值最小。

接下来的 M 行,每行包含 2 个整数,之间用一个空格隔开,依次表示在给定的 Si和

Xi下小 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≤10,000,-1,000,000,000≤Hi≤1,000,000,000,
0≤X0≤1,000,000,000,1≤Si≤N,0≤Xi≤1,000,000,000,数据保证Hi互不相同


不得不说这是倍增中难以多得的一道好题。
倍增用的真的太巧妙了!
我将此题分为三个部分讲解。

1.倍增预处理部分:

考虑如何倍增。
我们将 小A开一天 与 小B开一天 作为一轮。
那么 pos[ i ][ j ]表示从 i 出发,走了 2^j 轮后所到达的位置。
显然有:pos[ i ][ j ] = pos[ pos[i][j-1] ][ j-1 ];

那么定义 disA[ i ][ j ]为 从 i 出发行走 j 轮后,小A走的距离。disB[ i ][ j ]同理。
不难想到: disA[ i ][ j ] = disA[ i ][ j-1 ] + disA[ pos[i][j-1] ][ j-1 ]。disB同理。

所以Dist[ i ][ j ] = disA[ i ][ j ] + disB[ i ][ j ]。这在等会处理X的限制时用到(Dist[ i ][ j ]当然不用一个个算出来)。

一下就是倍增部分的核心代码了:

 //特别提醒一下,一定要先循环j,再循环i,这是一个很容易错的地方。
 //笔者做倍增时就经常被坑,关键是坑完以后还没有意识,每次都要找很久才找得出来。
 for(int j = 1; j <= LOG ; j ++) 
       for(int i = 1; i <= N ; i ++){
              pos[i][j] = pos[ pos[i][j-1] ][j-1];
              disA[i][j] = disA[i][j-1] + disA[ pos[i][j-1] ][j-1];
              disB[i][j] = disB[i][j-1] + disB[ pos[i][j-1] ][j-1];
        }

2.初始值的求解:

有了倍增式的转移,我们就可以“批量生产”disA , disB , pos 的值了
然后就会有一个问题:disA[ i ][ 0 ],disB[ i ][ 0 ],pos[ i ][ 0 ]怎么求呢?
对于这个问题,解法就有蛮多了。笔者在这里向大家推荐的是用 set 解决的方法。

对于 能来做提高组第三题 的大佬来说,维护动态的最小值、次小值肯定是so easy 啦。
这里就不说了,给个板子代码(笔者自己的板子啦……):

//sub[1] = sub[0] = INF;  //初始化
inline void Modify(int date)    //维护动态最小值和次小值,date为当前插入元素
{
    //sub[1]为最小值,sub[0]为次小值
    if(date <=sub[1]){
        sub[0] = sub[1];  sub[1] = date;
    }
    else if(date < sub[0])sub[0] = date;
    return;
}

那么我们就利用Set的有序性,不断的向其中插入。
答案更新于 pos_now-2 , pos_now-1 , pos_now+1 , pos_now+2 。
具体流程如下:

for(i = N -> 1)                                                //倒推
   Insert( now_item )
   Find( now_item )  ---->  get pos                            //获得当前元素的位置
   重复2遍:  if(pos!= Begin){ pos<--1 , modify(pos_now) ;}     //更新pos_now-2 ,pos_now-1 
   重复2遍:  if(pos-->1 != End){ modify(pos_now);  pos-->1;}   //更新pos_now+1 , pos_now+2
   利用得到的sub[1],sub[0]更新答案。

一路更新下去,就可以得到:
从 i 出发:
1> 小A走一步后的位置 apos
2> 小B走一步后的位置 bpos
3> 小A走一步的距离 A
4> 小B走一步的距离 B
用这些东西不难推出初始值了:
pos[i][0] = bpos[ apos[i] ] ; disA[i][0] = A[i] ; disB[i][0] = B[ apos[i] ] ;
这样我们就可以求得我们所需的所有初始值了。


3.对于给定 Si , Xi 查找距离:
这部分就是倍增的基础,不讲
唯一需要注意的是,在跳转完后,需要单独考虑 小A 是否能再向前一步(0.5轮)。
这一步处理的代码:
if(A[pos] <= Limit && apos[pos]!=0)Distant_A += A[pos];


在最后,给出完整的实现代码:

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<set>
#include<vector>
#define IL inline
#define RG register
#define LOG 17
#define maxn 100005
#define INF 2000000100
#define ll long long
using namespace std;

IL int gi()
{
    RG int date = 0,m = 1; RG char ch = 0;
    while(ch!='-'&&(ch<'0'||ch>'9'))ch = getchar();
    if(ch == '-'){m = -1 ; ch = getchar();}
    while('0'<=ch && ch<='9'){
        date = date * 10 + ch - '0';
        ch = getchar();
    }return date * m;
}
IL ll gi_ll()
{
    RG ll date = 0,m = 1; RG char ch = 0;
    while(ch!='-'&&(ch<'0'||ch>'9'))ch = getchar();
    if(ch == '-'){m = -1 ; ch = getchar();}
    while('0'<=ch && ch<='9'){
        date = date * 10 + ch - '0';
        ch = getchar();
    }return date * m;
}
void write(ll ac){
    if(ac<0){putchar('-'); ac = -ac;}
    if(ac>9)write(ac/10);
    putchar(ac%10+'0');
}

int N,M,ps;
int apos[maxn],bpos[maxn],sd[3],pos[maxn][LOG+2];   
ll disA[maxn][LOG+2],disB[maxn][LOG+2],sub[3];
ll DA,DB,A[maxn],B[maxn];
struct hight{
    ll h;int id;
    bool operator <(hight b)const{    //重载定义结构体的优先级
        return h < b.h;}
}H[maxn];
set<hight>S;
set<hight>::iterator it;
set<hight>::iterator is;
IL ll abs1(ll ag,ll bg){if(ag - bg > 0)return ag - bg; else return bg - ag;}

IL void memses()
{
    H[0].h = INF;
    for(int i = 0; i <= N ; i ++)
        for(int j = 0; j <= LOG ; j ++){
            disA[i][j] = INF;
            disB[i][j] = INF;
        }
    return;
}

IL void modify(hight G,int i)    //修改最小值和次小值
{
    ll hg = abs1(G.h,H[i].h); int ID = G.id;
    if(hg<sub[1] || (hg == sub[1] && H[ID].h<H[sd[1]].h)){
        sd[0] = sd[1]; sub[0] = sub[1];
        sub[1] = hg; sd[1] = ID;
    }
    else
    if(hg<sub[0] || (hg == sub[0] && H[ID].h<H[sd[0]].h)){
        sd[0] = ID;
        sub[0] = hg;
    }return;
}

IL void pre_do()  
{
    S.clear();
    for(int i = N ; i >= 1 ; i --)   
    {
        S.insert(H[i]);
        sub[1] = sub[0] = INF;
        sd[1] = sd[0] = 0;
        it = S.find(H[i]); is = it;it++;
        if(is!=S.begin()){is--; modify(*is,i); }
        if(is!=S.begin()){is--; modify(*is,i); }
        if(it!=S.end()){modify(*it,i); it++; }
        if(it!=S.end()){modify(*it,i); }
        bpos[i] = sd[1]; apos[i] = sd[0];
        B[i] = sub[1]; A[i] = sub[0];
    }  
    return;
}

IL void DB_work()                      //倍增搞事情
{
    for(int i = 1; i <= N ; i ++){
       disA[i][0] = A[i];  disB[i][0] = B[apos[i]]; 
       pos[i][0] = bpos[apos[i]];
    }
    for(int j = 1; j <= LOG ; j ++)  //关键:先j再i,注意倍增循环顺序!!
       for(int i = 1; i <= N ; i ++){
            pos[i][j] = pos[ pos[i][j-1] ][j-1];
            disA[i][j] = disA[i][j-1] + disA[ pos[i][j-1] ][j-1];
            disB[i][j] = disB[i][j-1] + disB[ pos[i][j-1] ][j-1];
        }
    return;
}

IL void GetDist(int ps,ll Limit)  //DA、DB即A、B的行驶距离
{
    for(int i = LOG ; i >= 0 ; i --){
        if(disA[ps][i] + disB[ps][i] <= Limit && pos[ps][i]!=0){
            DA += disA[ps][i];
            DB += disB[ps][i];
            Limit -= (disA[ps][i] + disB[ps][i]);
            ps = pos[ps][i];
        }
    }
    if(A[ps] <= Limit && apos[ps]!=0)DA += A[ps];
    return;
}

int main()
{
    freopen("drive.in","r",stdin);
    freopen("drive.out","w",stdout);
    N = gi();
    for(int i = 1; i <= N ; i ++)H[i].h = gi_ll();
    for(int i = 1; i <= N ; i ++)H[i].id = i;
    memses();   //初始化
    pre_do();   //求解初始值
    DB_work();  //倍增预处理

    //solve problem1:
    ll X0 = gi_ll(),ans = 0;double pp = INF,ss;
    for(int i = 1; i <= N ; i ++)
    {
        ps = i; DA = DB = 0;
        GetDist(i,X0);
        if(DB == 0)ss = INF - 1;
        else ss = ((1.0*DA) / (1.0*DB));
        if(ss < pp){ans = i; pp = ss;}
        else if(ss == pp && H[i].h > H[ans].h){ans = i; pp = ss;}

    //solve problem2:
    M = gi();
    for(int i = 1; i <= M ; i ++){
        int Si = gi();ll Xi = gi_ll();
        DA = DB = 0;
        GetDist(Si,Xi);
        write(DA); putchar(' ');
        write(DB); putchar('\n');
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值