Loading

【题解】Luogu P1081 [NOIP2012 提高组] 开车旅行

1. 海拔预处理

因为小 A 和小 B 开车涉及选择最短距离的问题,所以我们需要把他们两个开到每个城市要选择的最短距离预处理出来。

朴素做法用 \(O(n^2)\) 打擂台求出最近和第二近的城市,但显然过不了。我们可以这么做:

  • 读入时存储每个城市的高度和编号。
  • 读入完成后按照高度排序。此时与排序后的城市 \(i\) 最近与第二近的城市分别可能是 \(i-1\)\(i+1\)\(i-2\)\(i+2\)
  • 使用链表维护这些城市的高度,按原始编号把与节点以高度的顺序连接起来。
  • 在链表上按题目要求求出距离每个城市最近与第二近的城市,放在两个数组里。

实现代码如下:

void initList(){
	sort(p+1,p+1+n,cmp);
	for(int i=1;i<=n;i++){
		cl[p[i]].l=c[p[i-1]].cnt;
		cl[p[i]].r=c[p[i+1]].cnt;
	}
	for(int i=1;i<=n;i++){
		if(!cl[i].l&&cl[i].r){
			minh[i]=cl[i].r;
			if(cl[minh[i]].r){
				minh2[i]=cl[minh[i]].r;
			}
		}else if(!cl[i].r&&cl[i].l){
			minh[i]=cl[i].l;
			if(cl[minh[i]].l){
				minh2[i]=cl[minh[i]].l;
			}
		}else if(cl[i].l&&cl[i].r){
			if(c[i].h-c[cl[i].l].h<=c[cl[i].r].h-c[i].h){
				minh[i]=cl[i].l;
				if(!cl[cl[i].l].l) minh2[i]=cl[i].r;
				else if(c[i].h-c[cl[cl[i].l].l].h<=c[cl[i].r].h-c[i].h){
					minh2[i]=cl[cl[i].l].l;
				}else{
					minh2[i]=cl[i].r;
				}
			}else{
				minh[i]=cl[i].r;
				if(!cl[cl[i].r].r) minh2[i]=cl[i].l;
				else if(c[i].h-c[cl[i].l].h<=c[cl[cl[i].r].r].h-c[i].h){
					minh2[i]=cl[i].l;
				}else{
					minh2[i]=cl[cl[i].r].r;
				}
			}
		}
		cl[cl[i].l].r=cl[i].r;
		cl[cl[i].r].l=cl[i].l;
	}
}

此部分时间复杂度为 \(O(n)\)

2. 倍增预处理

很明显,每次开车暴搜是不现实的,时间复杂度会变成 \(O(mn)\)。但是因为开车只能向东开,我们可以使用倍增法来预处理每个区间旅行小 A 和小 B 开的公里数。

\(f_{i,k}\) 为从 \(i\) 城市出发,开 \(2^k\) 个循环所需要记录的信息包括小 A 和小 B 开的公里数和到达的城市。每一个循环定义为小 A 和小 B 轮流一次。

于是我们可以写出这样一段预处理代码:

void bl(){
	for(int i=1;i<=n;i++){
		if(!minh2[i]){
			f[i][0].ra=0;
		}else{
			f[i][0].ra=abs(c[minh2[i]].h-c[i].h);
		}
		if(!minh[minh2[i]]){
			f[i][0].rb=0;
		}else{
			f[i][0].rb=abs(c[minh2[i]].h-c[minh[minh2[i]]].h);
		}
		f[i][0].ep=minh[minh2[i]];
	}
	for(int k=1;k<=log(n)/log(2);k++){
		for(int i=1;i<=n;i++){
			if(f[i][k-1].ep&&f[f[i][k-1].ep][k-1].ep){
				f[i][k].ra=f[i][k-1].ra+f[f[i][k-1].ep][k-1].ra;
				f[i][k].rb=f[i][k-1].rb+f[f[i][k-1].ep][k-1].rb;
				f[i][k].ep=f[f[i][k-1].ep][k-1].ep; 
			}
		}
	}
}

这一部分的时间复杂度是典型的倍增 \(O(n\log n)\)

3. 求解

预处理完成之后就可以求解了。这一部分与 LCA 类似,从大到小遍历 \(k\),如果 \(f_{s,k}\) 的路程不超过限定的 \(x\) 那么就往前走,否则继续减小 \(k\)。如果可行开车的天数不是 \(2\) 的倍数,也就是两个人能完成一个完整的循环时,最后就要再加上小 A 能开的最后一段路。加一个特判即可。

void calc(int s,int x){
	ta=tb=0;
	for(int k=log(n)/log(2);k>=0;k--){
		if(f[s][k].ep&&f[s][k].ra+f[s][k].rb<=x){
			ta+=f[s][k].ra;
			tb+=f[s][k].rb;
			x-=f[s][k].ra+f[s][k].rb;
			s=f[s][k].ep;
		}
	}
	if(minh2[s]&&abs(c[s].h-c[minh2[s]].h)<=x){
		ta+=abs(c[s].h-c[minh2[s]].h);
	}
}

此部分时间复杂度与 LCA 类似,\(O(\log n)\)

Part 4: 完成 Task 1&Task 2

有了上面的代码这两个问题就都能够迎刃而解了。

Task 1

这个问题遍历每个节点并求解,用一个 double 类型变量记录比值的最小值,找到这个最小值。如果最小值相同那么找海拔更高的。如果小 B 的路程为 \(0\) 就单独特判,记录海拔更高的。

void tsk1(){
	minb=9223372036854775807;
	for(int i=1;i<=n;i++){
		calc(i,x0);
		if(tb!=0){
			if(1.0*ta/tb<minb){
				minb=1.0*ta/tb;
				ans1=i;
			}else if(1.0*ta/tb==minb){
				if(c[i].h>c[ans1].h) ans1=i;
			}
		}else{
			if(minb==9223372036854775807&&c[i].h>c[ans1].h) ans1=i;
		}
	}
	printf("%lld\n",ans1);
}

时间复杂度 \(O(n\log n)\)

Task 2

这个问题就更简单了,依次求解 \(m\) 个问题即可。

void tsk2(){
	for(int i=1;i<=m;i++){
		calc(s[i],x[i]);
		printf("%lld %lld\n",ta,tb);
	}
}

总结

此题思维不是特别难,但是细节较多,码量很大。

总时间复杂度应该是 \(O((n+m)\log n)\)

完整代码:

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#define int long long
using namespace std;
const int maxn=1e5+10;
struct city{
	int h,cnt;
}c[maxn];
struct List{
	int l,r;
}cl[maxn];
struct Node{
	int ra,rb,ep;
}f[maxn][40]; 
int n,m,x0,ta,tb,ans1;
double minb;
int s[maxn],x[maxn],p[maxn];
int minh[maxn],minh2[maxn];
bool cmp(int a,int b){
	return c[a].h<c[b].h;
}
void Read(){
	scanf("%lld",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld",&c[i].h);
		c[i].cnt=p[i]=i;
	}
	scanf("%lld%lld",&x0,&m);
	for(int i=1;i<=m;i++){
		scanf("%lld%lld",&s[i],&x[i]);
	}
}
void initList(){
	sort(p+1,p+1+n,cmp);
	for(int i=1;i<=n;i++){
		cl[p[i]].l=c[p[i-1]].cnt;
		cl[p[i]].r=c[p[i+1]].cnt;
	}
	for(int i=1;i<=n;i++){
		if(!cl[i].l&&cl[i].r){
			minh[i]=cl[i].r;
			if(cl[minh[i]].r){
				minh2[i]=cl[minh[i]].r;
			}
		}else if(!cl[i].r&&cl[i].l){
			minh[i]=cl[i].l;
			if(cl[minh[i]].l){
				minh2[i]=cl[minh[i]].l;
			}
		}else if(cl[i].l&&cl[i].r){
			if(c[i].h-c[cl[i].l].h<=c[cl[i].r].h-c[i].h){
				minh[i]=cl[i].l;
				if(!cl[cl[i].l].l) minh2[i]=cl[i].r;
				else if(c[i].h-c[cl[cl[i].l].l].h<=c[cl[i].r].h-c[i].h){
					minh2[i]=cl[cl[i].l].l;
				}else{
					minh2[i]=cl[i].r;
				}
			}else{
				minh[i]=cl[i].r;
				if(!cl[cl[i].r].r) minh2[i]=cl[i].l;
				else if(c[i].h-c[cl[i].l].h<=c[cl[cl[i].r].r].h-c[i].h){
					minh2[i]=cl[i].l;
				}else{
					minh2[i]=cl[cl[i].r].r;
				}
			}
		}
		cl[cl[i].l].r=cl[i].r;
		cl[cl[i].r].l=cl[i].l;
	}
}
void bl(){
	for(int i=1;i<=n;i++){
		if(!minh2[i]){
			f[i][0].ra=0;
		}else{
			f[i][0].ra=abs(c[minh2[i]].h-c[i].h);
		}
		if(!minh[minh2[i]]){
			f[i][0].rb=0;
		}else{
			f[i][0].rb=abs(c[minh2[i]].h-c[minh[minh2[i]]].h);
		}
		f[i][0].ep=minh[minh2[i]];
	}
	for(int k=1;k<=log(n)/log(2);k++){
		for(int i=1;i<=n;i++){
			if(f[i][k-1].ep&&f[f[i][k-1].ep][k-1].ep){
				f[i][k].ra=f[i][k-1].ra+f[f[i][k-1].ep][k-1].ra;
				f[i][k].rb=f[i][k-1].rb+f[f[i][k-1].ep][k-1].rb;
				f[i][k].ep=f[f[i][k-1].ep][k-1].ep; 
			}
		}
	}
}
void calc(int s,int x){
	ta=tb=0;
	for(int k=log(n)/log(2);k>=0;k--){
		if(f[s][k].ep&&f[s][k].ra+f[s][k].rb<=x){
			ta+=f[s][k].ra;
			tb+=f[s][k].rb;
			x-=f[s][k].ra+f[s][k].rb;
			s=f[s][k].ep;
		}
	}
	if(minh2[s]&&abs(c[s].h-c[minh2[s]].h)<=x){
		ta+=abs(c[s].h-c[minh2[s]].h);
	}
}
void tsk1(){
	minb=9223372036854775807;
	for(int i=1;i<=n;i++){
		calc(i,x0);
		if(tb!=0){
			if(1.0*ta/tb<minb){
				minb=1.0*ta/tb;
				ans1=i;
			}else if(1.0*ta/tb==minb){
				if(c[i].h>c[ans1].h) ans1=i;
			}
		}else{
			if(minb==9223372036854775807&&c[i].h>c[ans1].h) ans1=i;
		}
	}
	printf("%lld\n",ans1);
}
void tsk2(){
	for(int i=1;i<=m;i++){
		calc(s[i],x[i]);
		printf("%lld %lld\n",ta,tb);
	}
}
signed main(){
	Read();
	initList();
	bl();
	tsk1();
	tsk2();
	return 0;
}
posted @ 2025-12-12 23:03  Seqfrel  阅读(0)  评论(0)    收藏  举报