开车旅行(NOIP2012提高组)

# [NOIP2012 提高组] 开车旅行

## 题目描述

小 $\text{A}$ 和小 $\text{B}$ 决定利用假期外出旅行,他们将想去的城市从 $1 $ 到 $n$ 编号,且编号较小的城市在编号较大的城市的西边,已知各个城市的海拔高度互不相同,记城市 $i$ 的海拔高度为$h_i$,城市 $i$ 和城市 $j$ 之间的距离 $d_{i,j}$ 恰好是这两个城市海拔高度之差的绝对值,即 $d_{i,j}=|h_i-h_j|$。
 
旅行过程中,小 $\text{A}$ 和小 $\text{B}$ 轮流开车,第一天小 $\text{A}$ 开车,之后每天轮换一次。他们计划选择一个城市 $s$ 作为起点,一直向东行驶,并且最多行驶 $x$ 公里就结束旅行。    

小 $\text{A}$ 和小 $\text{B}$ 的驾驶风格不同,小 $\text{B}$ 总是沿着前进方向选择一个最近的城市作为目的地,而小 $\text{A}$ 总是沿着前进方向选择第二近的城市作为目的地(注意:本题中如果当前城市到两个城市的距离相同,则认为离海拔低的那个城市更近)。如果其中任何一人无法按照自己的原则选择目的城市,或者到达目的地会使行驶的总距离超出 $x$ 公里,他们就会结束旅行。

在启程之前,小 $\text{A}$ 想知道两个问题:

1、 对于一个给定的 $x=x_0$,从哪一个城市出发,小 $\text{A}$ 开车行驶的路程总数与小 $\text{B}$ 行驶的路程总数的比值最小(如果小 $\text{B}$ 的行驶路程为 $0$,此时的比值可视为无穷大,且两个无穷大视为相等)。如果从多个城市出发,小 $\text{A}$ 开车行驶的路程总数与小 $\text{B}$ 行驶的路程总数的比值都最小,则输出海拔最高的那个城市。

2、对任意给定的 $x=x_i$ 和出发城市 $s_i$,小 $\text{A}$ 开车行驶的路程总数以及小 $\text B$ 行驶的路程总数。

## 输入格式

第一行包含一个整数 $n$,表示城市的数目。

第二行有 $n$ 个整数,每两个整数之间用一个空格隔开,依次表示城市 $1$ 到城市 $n$ 的海拔高度,即 $h_1,h_2 ... h_n$,且每个 $h_i$ 都是互不相同的。

第三行包含一个整数 $x_0$。

第四行为一个整数 $m$,表示给定 $m$ 组 $s_i$ 和 $x_i$。

接下来的 $m$ 行,每行包含 $2$ 个整数 $s_i$ 和 $x_i$,表示从城市$s_i$ 出发,最多行驶 $x_i$ 公里。

## 输出格式

输出共 $m+1$ 行。

第一行包含一个整数 $s_0$,表示对于给定的 $x_0$,从编号为 $s_0$ 的城市出发,小 $\text A$ 开车行驶的路程总数与小 $\text B$ 行驶的路程总数的比值最小。

接下来的 $m$ 行,每行包含 $2$ 个整数,之间用一个空格隔开,依次表示在给定的 $s_i$ 和 $x_i$ 下小 $\text A$ 行驶的里程总数和小 $\text B$ 行驶的里程总数。

## 样例 #1

### 样例输入 #1

```

2 3 1 4 


1 3 
2 3 
3 3 
4 3
```

### 样例输出 #1

```

1 1 
2 0 
0 0 
0 0
```

## 样例 #2

### 样例输入 #2

```
10 
4 5 6 1 2 3 7 8 9 10 

10 
1 7 
2 7 
3 7 
4 7 
5 7 
6 7 
7 7 
8 7 
9 7 
10 7
```

### 样例输出 #2

```

3 2 
2 4 
2 1 
2 4 
5 1 
5 1 
2 1 
2 0 
0 0 
0 0
```

## 提示

【样例1说明】

![](https://cdn.luogu.com.cn/upload/image_hosting/zgms0k7y.png)

各个城市的海拔高度以及两个城市间的距离如上图所示。

如果从城市 $1$ 出发,可以到达的城市为 $2,3,4$,这几个城市与城市 $1$ 的距离分别为 $1,1,2$,但是由于城市 $3$ 的海拔高度低于城市 $2$,所以我们认为城市 $3$ 离城市 $1$ 最近,城市 $2$ 离城市 $1$ 第二近,所以小A会走到城市 $2$。到达城市 $2$ 后,前面可以到达的城市为 $3,4$,这两个城市与城市 $2$  的距离分别为 $2,1$,所以城市 $4$ 离城市 $2$ 最近,因此小B会走到城市$4$。到达城市 $4$ 后,前面已没有可到达的城市,所以旅行结束。

如果从城市 $2$ 出发,可以到达的城市为 $3,4$,这两个城市与城市 $2$ 的距离分别为 $2,1$,由于城市 $3$ 离城市 $2$ 第二近,所以小 $\text A$ 会走到城市 $3$。到达城市 $3$ 后,前面尚未旅行的城市为 $4$,所以城市 $4$ 离城市 $3$ 最近,但是如果要到达城市 $4$,则总路程为 $2+3=5>3$,所以小 $\text B$ 会直接在城市 $3$ 结束旅行。

如果从城市 $3$ 出发,可以到达的城市为 $4$,由于没有离城市 $3$ 第二近的城市,因此旅行还未开始就结束了。

如果从城市 $4$ 出发,没有可以到达的城市,因此旅行还未开始就结束了。


【样例2说明】

当 $x=7$ 时,如果从城市 $1$ 出发,则路线为 $1 \to 2 \to 3 \to 8 \to 9$,小 $\text A$ 走的距离为 $1+2=3$,小 $\text B$ 走的距离为 $1+1=2$。(在城市 $1$ 时,距离小 $\text A$ 最近的城市是 $2$ 和 $6$,但是城市 $2$ 的海拔更高,视为与城市 $1$ 第二近的城市,所以小 $\text A$ 最终选择城市 $2$;走到$9$ 后,小 $\text A$ 只有城市 $10$ 可以走,没有第二选择可以选,所以没法做出选择,结束旅行)

如果从城市 $2$ 出发,则路线为 $2 \to 6 \to 7$,小 $\text A$ 和小 $\text B$ 走的距离分别为 $2,4$。

如果从城市 $3$ 出发,则路线为 $3 \to 8 \to 9$,小 $\text A$ 和小 $\text B$ 走的距离分别为$2,1$。

如果从城市 $4$ 出发,则路线为 $4 \to 6 \to 7$,小 $\text A$ 和小 $\text B$ 走的距离分别为 $2,4$。

如果从城市 $5$ 出发,则路线为 $5 \to 7 \to 8$,小 $\text A$ 和小 $\text B$ 走的距离分别为 $5,1$。

如果从城市 $6$ 出发,则路线为 $6 \to 8 \to 9$,小 $\text A$ 和小 $\text B$ 走的距离分别为$5,1$。

如果从城市 $7$ 出发,则路线为 $7 \to 9 \to 10$,小 $\text A$ 和小 $\text B$ 走的距离分别为$2,1$。

如果从城市 $8$ 出发,则路线为 $8 \to 10$,小 $\text A$ 和小 $\text B$ 走的距离分别为$2,0$。

如果从城市 $9$ 出发,则路线为 $9$,小 $\text A$ 和小 $\text B$ 走的距离分别为 $0,0$(旅行一开始就结束了)。

如果从城市 $10$ 出发,则路线为 $10$,小 $\text A$ 和小 $\text B$ 走的距离分别为$0,0$。

从城市 $2$ 或者城市 $4$ 出发小 $\text A$ 行驶的路程总数与小 $\text B$ 行驶的路程总数的比值都最小,但是城市 $2$ 的海拔更高,所以输出第一行为 $2$。

【数据范围与约定】  

对于 $30\%$ 的数据,有$1\le n \le 20,1\le m\le 20$;  
对于$40\%$ 的数据,有$1\le n \le 100,1\le m\le 100$;  
对于 $50\%$ 的数据,有$1\le n \le 100,1\le m\le 1000$;  
对于 $70\%$ 的数据,有$1\le n \le 1000,1\le m\le 10^4$;   
对于 $100\%$ 的数据:$1\le n,m \le 10^5$,$-10^9 \le h_i≤10^9$,$1 \le s_i \le n$,$0 \le x_i \le 10^9$   
数据保证 $h_i$ 互不相同。

这道题最基本的思路是用倍增,但是其实它的难点在预处理部分。
倍增的部分此次就不细说了,和之前的最近公共祖先的思想类似。
我们主要来探讨一下预处理的部分。
我们需要预处理出每个城市小A和小B的选择目标和对应的距离,接下来就可以处理出进行2k轮开车的目的地和距离了。所以前者才是重中之重,而前者如果要用暴力的方法会tle的。
有人可能会疑惑,我们找当前点的后面两三个不就可以了?为什么会tle呢?
实际上并不是序号相差很远距离就很远,实际上有可能第一个城市和最后一个城市最近,可以举个例子,城市海拔如下:
1 3 4 5 6 7 8 9 10 11 2
也许你也会有疑问,不是说右边的城市在左边城市的东边吗?这可能吗?
注意这题并不是线性的,而是2D的。所以可以画出如下的图:


所以,这么看来,暴力算法的复杂度就是n方,就会超时。
所以我们需要换种方法。
我们不妨换种顺序,因为是用海拔来决定高度,所以海拔相距最近的,一定是距离最近的。
所以我们可以按照海拔进行排序,排好序后就按照这个顺序建立双向链表。
这时我们就知道了,对于某一城市,小a和小b要么没有选择,要么选择的城市一定在当前城市链表中的前两个和两个中。
为什么不是前一个和后一个?因为如果一个点在链表边上,它就只有一侧,所以把特殊情况考虑在内,就是前两个和后两个。
那么你可能又会觉得奇怪,万一这几个当中有城市在当前城市的西边怎么办?这样这个范围就可能不够了?
所以我们的处理顺序就成了很大的很关键,我们按照城市编号顺序去处理每个城市的目标,处理完后就把当前城市删除(这也是用双向链表的原因),这样每次处理就可以保证当前链表中的其他城市均在当前城市的东边,而保证我们所取的范围是够的。
当然你也许还会有疑问,那我怎么定位要处理的城市在链表中的位置呢?
这个准备的指针数组就好了。
这样就可以把预处理的复杂度从O(n2)降低到O(n),这样就可以过了
下面是参考代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define MAX 2000000000
using namespace std;
const int maxn=100005;
struct City{
	int num;
	int h;
	City *last,*next; 
}city[maxn]; 
City *pos[maxn];
long long n,m,x0,ss[maxn],xx[maxn],h[maxn],maxk; 
long long aaim[maxn],baim[maxn],adis[maxn],bdis[maxn];
long long longdis[maxn][20],longaim[maxn][20];
long long alongdis[maxn][20],alongaim[maxn][20];
long long s0,da=-1,db=-1;
void fun1(long long curs){
	long long tot=0,cura=0,curb=0,curn=curs;
	for(int k=maxk;k>=0;k--){
		if(longaim[curn][k]!=0&&longdis[curn][k]+tot>x0){
			continue;
		}
		if(longaim[curn][k]==0){
			continue;
		}
		tot+=longdis[curn][k];
		cura+=alongdis[curn][k];
		curn=longaim[curn][k];
	}
	curb=tot-cura;
	if(aaim[curn]!=0&&tot+adis[curn]<=x0){
		tot+=adis[curn];
		cura+=adis[curn];
	}
	if(da==-1){
		s0=curs;
		da=cura;
		db=curb;
	}else if(db==0&&curb==0&&h[curs]>h[s0]){
		s0=curs;
		da=cura;
		db=curb;
	}else if(db==0){
		s0=curs;
		da=cura;
		db=curb;
	}else if(curb==0){
		
	}else if(da*curb>db*cura){
		s0=curs;
		da=cura;
		db=curb;
	}else if(da*curb==db*cura&&h[curs]>h[s0]){
		s0=curs;
		da=cura;
		db=curb;
	} 
}
void fun2(long long s,long long xi){
	long long tot=0,cura=0,curb=0,curn=s;
	for(int k=maxk;k>=0;k--){
		if(longaim[curn][k]!=0&&longdis[curn][k]+tot>xi){
			continue;
		}
		if(longaim[curn][k]==0){
			continue;
		}
		tot+=longdis[curn][k];
		cura+=alongdis[curn][k];
		curn=longaim[curn][k];
	}
	curb=tot-cura;
	if(aaim[curn]!=0&&tot+adis[curn]<=xi){
		tot+=adis[curn];
		cura+=adis[curn];
	}
	printf("%d %d\n",cura,curb); 
}
int cmp(City a,City b){
	return a.h<b.h;
}
void update(City cur,City aim){
	if((abs(cur.h-aim.h)<bdis[cur.num])||(abs(cur.h-aim.h)==bdis[cur.num]&&aim.h<pos[baim[cur.num]]->h)){
		adis[cur.num]=bdis[cur.num];
		aaim[cur.num]=baim[cur.num];
		bdis[cur.num]=abs(cur.h-aim.h);
		baim[cur.num]=aim.num;
	}else if((abs(cur.h-aim.h)<adis[cur.num])||(abs(cur.h-aim.h)==adis[cur.num]&&aim.h<pos[aaim[cur.num]]->h)){
		adis[cur.num]=abs(cur.h-aim.h);
		aaim[cur.num]=aim.num;
	}
}
int main(){
	scanf("%lld",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld",&city[i].h);
		adis[i]=bdis[i]=MAX;
		city[i].num=i;
	}
	scanf("%lld%lld",&x0,&m);
	for(int i=0;i<m;i++){
		scanf("%lld%lld",&ss[i],&xx[i]);
	}
	sort(city+1,city+n+1,cmp);

	for(int i=1;i<=n;i++){
		if(i==1){
			city[i].last=NULL;
		}else{
			city[i].last=&city[i-1];
		}
		if(i==n){
			city[i].next=NULL;
		}else{
			city[i].next=&city[i+1];
		}
		pos[city[i].num]=&city[i];
	}

	for(int i=1;i<=n;i++){
		City *p;
		p=pos[i]->last;
		if(p!=NULL){
			update(*pos[i],*p);
			p=p->last;
			if(p!=NULL){
				update(*pos[i],*p);
			}
		}
		p=pos[i]->next;
		if(p!=NULL){
			update(*pos[i],*p);
			p=p->next;
			if(p!=NULL){
				update(*pos[i],*p);
			}
		}
		if(pos[i]->next!=NULL){
			pos[i]->next->last=pos[i]->last;
		}
		if(pos[i]->last!=NULL){
			pos[i]->last->next=pos[i]->next;
		}
		 
	} 
	for(int i=1;i<=n;i++){
		if(aaim[i]!=0&&baim[aaim[i]]!=0){
			longdis[i][0]=adis[i]+bdis[aaim[i]];
			longaim[i][0]=baim[aaim[i]];
			alongdis[i][0]=adis[i];
			alongaim[i][0]=baim[aaim[i]];
		}else{
			longaim[i][0]=0;
			longdis[i][0]=MAX;
			alongdis[i][0]=MAX;
			alongaim[i][0]=0;
		}
	}
	for(int k=1;(1<<k)<=n;k++){
		for(int i=1;i<=n;i++){
			if(longaim[i][k-1]!=0&&longaim[longaim[i][k-1]][k-1]!=0){
				longdis[i][k]=longdis[i][k-1]+longdis[longaim[i][k-1]][k-1];
				longaim[i][k]=longaim[longaim[i][k-1]][k-1];
			}else{
				longdis[i][k]=MAX;
				longaim[i][k]=0;
			}
			if(alongaim[i][k-1]!=0&&alongaim[alongaim[i][k-1]][k-1]!=0){
				alongdis[i][k]=alongdis[i][k-1]+alongdis[alongaim[i][k-1]][k-1];
				alongaim[i][k]=alongaim[alongaim[i][k-1]][k-1];
			}else{
				alongdis[i][k]=MAX;
				alongaim[i][k]=0;
			}
			
		}
		maxk=k;
	}	
	for(int i=1;i<=n;i++){
		fun1(i);
	}
	printf("%d\n",s0);
	for(int i=0;i<m;i++){
		fun2(ss[i],xx[i]);
	}
	return 0;
} 

  • 10
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值