正文
第二题:开车旅行
这题贼难~。
首先我们要知道当前点的最短路和次短路的长度和所跳到位置,这样我们用倍增才好解决。。。
我们就想着把它排序,因为排完序之后他的绝对值的最小值一定存在于排完序队列中的上一个(last),上上个(lastest),下一个(next)和下下个(nextest)。(四个东西)
那么我们又要保证当前的 这四个东西 都 存在于 当前点 的 东边。什么数据结构可以 很好地 满足 这种删点和访问左右端点操作呢?(splay前驱后继???)
其实我们用链表就可以了。每次找出那四个东西,然后再比较他们与当前高度差的绝对值,优先选绝对值小的 和 高度小的,更新一下,然后用4个数组记录一下最小值,最小值所取到的位置,次小值以及它的位置。删除这个点即可,当然要考虑全面。
代码<看不懂的评论问我哦~~我尽量讲明白>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
int n;
int k;
struct node{
long long h;
int x;
bool operator<(const node y)const{
return h<y.h;
}
}p[100010];
struct nodenew{
long long h;
int last,lastest,next,nextest;
}s[100010];
long long dmin[100010],cmin[100010];
int wh[100010],whc[100010];
long long f[100010][20];
struct bloo{
long long a,b,tot;
}tot[100010][20];
long long a=0,b=0;
void go(int i){
int next=s[i].next,nextest=s[i].nextest;
int last=s[i].last,lastest=s[i].lastest;
s[next].last=last;s[next].lastest=lastest;
s[nextest].lastest=last;
s[last].next=next;s[last].nextest=nextest;
s[lastest].nextest=next;
}
void prepare(){
sort(p+1,p+1+n);
for(int i=1;i<=n;i++) {//用链表维护
s[p[i].x].h=p[i].h;//s为原序列,h为高度
if(i>=2) s[p[i].x].last=p[i-1].x;//原序列最近一个比它小的就是在排好的序列中的上一个(的原编号)。
if(i>=3) s[p[i].x].lastest=p[i-2].x;//同理
if(i+1<=n) s[p[i].x].next=p[i+1].x;
if(i+2<=n) s[p[i].x].nextest=p[i+2].x;
}
for(int i=1;i<=n;i++){
int nexta=s[i].next,nextesta=s[i].nextest;//先记录下来,方便操作。
int lasta=s[i].last,lastesta=s[i].lastest;
if(abs(s[lasta].h-s[i].h)<=abs(s[nexta].h-s[i].h)){//最短路可能在最近一个比我小的和最近一个比我大的。
dmin[i]=abs(s[lasta].h-s[i].h);wh[i]=lasta;//记录下来
if(abs(s[lastesta].h-s[i].h)<=abs(s[nexta].h-s[i].h)){cmin[i]=abs(s[lastesta].h-s[i].h);whc[i]=lastesta;}
else{cmin[i]=abs(s[nexta].h-s[i].h);whc[i]=nexta;}//再来记录次小值
}
else {
dmin[i]=abs(s[nexta].h-s[i].h);wh[i]=nexta;//记录最短路
if(abs(s[lasta].h-s[i].h)<=abs(s[nextesta].h-s[i].h)){cmin[i]=abs(s[lasta].h-s[i].h);whc[i]=lasta;}
else {cmin[i]=abs(s[nextesta].h-s[i].h);whc[i]=nextesta;}//记录次短路
}
go(i);//链表的维护,把这个点删去,因为后面的点不能往前走
}
}
void begina(){//预处理倍增
for(int i=1;i<=n;i++){
tot[i][0].tot=cmin[i]+dmin[whc[i]];//tot记录从i开始,走1轮的路程
tot[i][0].a=cmin[i];//走一轮后a的路程
tot[i][0].b=dmin[whc[i]];//走一轮后b的路程
f[i][0]=wh[whc[i]];//走一轮后的位置
}
for(int j=1;j<=16;j++)
for(int i=1;i<=n;i++){//不断维护f[i][j]
tot[i][j].tot=tot[i][j-1].tot+tot[f[i][j-1]][j-1].tot;
tot[i][j].a=tot[i][j-1].a+tot[f[i][j-1]][j-1].a;
tot[i][j].b=tot[i][j-1].b+tot[f[i][j-1]][j-1].b;
f[i][j]=f[f[i][j-1]][j-1];
}
}
double solve(long long x,long long k){
int now=x;
a=0;b=0;
for(int i=16;i>=0;i--){
if(k-tot[now][i].tot>=0){//能走就拼命走
k-=tot[now][i].tot;
a+=tot[now][i].a;
b+=tot[now][i].b;
now=f[now][i];
}
}
if(k>=cmin[now]) a+=cmin[now];//最后的A还可能在走一步
return (double)a/b;//返回AB的比值
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&p[i].h);//每个点记录一个高度
p[i].x=i;//编号是i
}
s[0].h=2e16;
prepare();//预处理函数
begina();//倍增预处理
scanf("%d",&k);
double mmin=2e16;
int minx=0;
for(int i=1;i<=n;i++){//枚举每个可能是解的城市
double pp=solve(i,k);//求解比值
if(mmin>pp){//更新mmin
minx=i;
mmin=pp;
}
}
printf("%d\n",minx);//输出
int m;
scanf("%d",&m);
for(int i=1;i<=m;i++){
long long x,y;//输入x,y
scanf("%lld %lld",&x,&y);
double p=solve(x,y);//求解
printf("%lld %lld\n",a,b);//输出a,b
}
}
当然我也没说不让你用multi_set做