P1081 开车旅行
大致题意
有\(n\)个城市,每个城市都有一个海拔\(h_i\),两个城市\(i,j\)之间的距离为\(|h_i-h_j|\),现在有两个人\(A\)和\(B\)轮流向东开车,\(A\)每次会选择一个距离第二近的城市,\(B\)每次会选择一个第一进的城市,当开到最后一个城市或距离超过\(x\)时停止开车,现在要求:
对于一个给定的\(x = x_0\),\(A\)开车距离与\(B\)开车的距离比值最小,(如果小 \(B\) 的行驶路程为 \(0\),此时的比值可视为无穷大,且两个无穷大视为相等),若有多个城市满足,输出海拔最低的那个
\(m\)次询问,每次给一个\(x\)和出发城市\(s\),求小\(A\)开车行驶的总路程数和小\(B\)开车行驶的总路程数
分析
可以发现很多城市下一次的开车决策是不变的,考虑先去把每个城市下一次开到的点预处理出来
先把所有的值排一次序,建立双向链表
从\([1,n]\)枚举每个城市
显然离城市\(i\)最近和次近的城市即为\(i-2,i-1,i+1,i+2\)中的两个
每次选完后把该城市删除即可
先来看第一问
直接枚举每个点,暴力模拟开车过程肯定是不行的,复杂度过不了
发现中间很多开车过程都是无用的,考虑倍增优化
设\(f_{i,j,0/1}\)表示从\(j\)城市出发,开车\(2^i\)天,\(A/B\)先开车开到的城市,\(dis_{0/1,i,j,0/1}\)表示\(A/B\)从城市\(j\)出发,开了\(2^i\)天,\(A/B\)形式的距离
根据倍增的性质(\(2^i = 2^{i-1}×2^{i-1}\)),有转移:
\(f_{i,j,k} = f_{i-1,f_{i-1,j,k},k}\)
\(dis_{0/1,i,j,k} = dis_{0/1,i-1,f_{i-1,j,k}}+dis_{0/1,i-1,j,k}\)
注意:\(i=1\)时前后两天不是同一个人开车,需要特判
枚举每个点,像\(LCA\)那样从大到小往前跳即可
操作2也同理
几个坑点
-
链表中要判定当前位置的前驱和后继不存在的情况
-
\(i=1\)时要特判
-
比值的除法要转化为乘法,否则会出现一些奇奇怪怪的精度问题
-
注意特判\(B\)开车距离为\(0\)时的情况
\(code\)
//细节有亿点多
#include<bits/stdc++.h>
#define now ((i==1)?k^1:k)//i=1时的特判
using namespace std;
const int MAXN = 100005;
#define int long long
int ansa = 1,ansb,best;
int pos[MAXN],n;
int f[30][MAXN][2],dis[2][30][MAXN][2];
int x0,s,x,m;
struct cc{
int min1,min2;
}drive[MAXN];
struct c{
int id,pre,next,val;
}city[MAXN];
bool cmp(c a,c b){
return a.val<b.val;
}
int g(int a,int b,int id){//选次大的城市
if(!a) return city[b].id;//特判不存在的情况
if(!b) return city[a].id;
if(city[id].val-city[a].val<=city[b].val-city[id].val)
return city[a].id;
else return city[b].id;
}
void del(int x){//删除操作
if(city[x].next) city[city[x].next].pre = city[x].pre;
if(city[x].pre) city[city[x].pre].next = city[x].next;
}
void prepare(){
sort(city+1,city+1+n,cmp);
for(int i=1;i<=n;i++) pos[city[i].id] = i,city[i].next = i+1,city[i].pre = i-1;//建立链表和映射
city[1].pre = 0,city[n].next = 0;
for(int i=1;i<n;i++){
int pre = city[pos[i]].pre,next = city[pos[i]].next;
if(pre&&(city[pos[i]].val - city[pre].val<=city[next].val-city[pos[i]].val||!next)){
drive[i].min2 = city[pre].id;
drive[i].min1 = g(city[pre].pre,next,pos[i]);
}
else{
drive[i].min2 = city[next].id;
drive[i].min1 = g(pre,city[next].next,pos[i]);
}
del(pos[i]);
}
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>city[i].val;
city[i].id = i;
}
prepare();
for(int i=1;i<=n;i++){//初始化
if(drive[i].min1){
f[0][i][0] = drive[i].min1;
dis[0][0][i][0] = abs(city[pos[i]].val - city[pos[drive[i].min1]].val);
dis[1][0][i][0] = 0;
}
if(drive[i].min2){
f[0][i][1] = drive[i].min2;
dis[0][0][i][1] = 0;
dis[1][0][i][1] = abs(city[pos[i]].val - city[pos[drive[i].min2]].val);
}
}
int t = ((log(n)/log(2)+1));
for(int i=1;i<=t;i++){//转移
for(int j=1;j<=n;j++){
for(int k=0;k<=1;k++){
if(f[i-1][j][k]) f[i][j][k] = f[i-1][f[i-1][j][k]][now];
if(f[i][j][k]){
dis[0][i][j][k] = dis[0][i-1][j][k]+dis[0][i-1][f[i-1][j][k]][now];
dis[1][i][j][k] = dis[1][i-1][j][k]+dis[1][i-1][f[i-1][j][k]][now];
}
}
}
}
cin>>x0;
for(int i=1;i<=n;i++){
int disa =0,disb = 0;
int k = 0,value = x0;
int s = i;
for(int j=t;j>=0;j--){
if(f[j][s][k]&&value>=dis[0][j][s][k]+dis[1][j][s][k]){//倍增跳
value-=dis[0][j][s][k]+dis[1][j][s][k];
disa+=dis[0][j][s][k],disb+=dis[1][j][s][k];
if(!j) k^=1;
s = f[j][s][k];
}
}
if(!disb) disa = 1;//disb为0时,比值为无限大
if(disa*ansb<disb*ansa){//除转乘
ansa = disa,ansb = disb,best = i;
}
else if(disa*ansb==disb*ansa&&city[pos[i]].val>city[pos[best]].val){//选海拔低的那个点
ansa = disa,ansb = disb,best = i;
}
}
cout<<best<<endl;
cin>>m;
while(m--){
int x0,s;
cin>>s>>x0;
int disa =0,disb = 0;
int k = 0,value = x0;
for(int j=t;j>=0;j--){
if(f[j][s][k]&&value>=dis[0][j][s][k]+dis[1][j][s][k]){
value-=dis[0][j][s][k]+dis[1][j][s][k];
disa+=dis[0][j][s][k],disb+=dis[1][j][s][k];
if(!j) k^=1;
s = f[j][s][k];
}
}
cout<<disa<<" "<<disb<<endl;
}
}

浙公网安备 33010602011771号