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;
	}
}
posted @ 2020-10-02 11:38  xcxc82  阅读(113)  评论(0)    收藏  举报