题目描述
小 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;
}