[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\) 行驶的路程总数。

数据范围与约定

对于 \(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\) 开车两种情况讨论,都会唯一的指向下一个位置。但是,每一个位置却不一定被唯一的位置指向。

我们考虑倒着建边,把被指向的节点看做父亲,边权就是两点间的距离,于是我们就得到了一张树形的图。而从点 \(u\) 向后走的过程即相当于从某一个点像根跳的过程,这个过程有一点像倍增往上跳动的过程。

但是这和普通的倍增有一些不太一样,因为实际上我们得到了两层图,一层是 \(A\),一层是 \(B\),每向上跳一次就会在图层间进行一次转换,所以如何处理倍增的信息呢?

幸运的是,发现对于一个点 \(u\),建设处理 \(A\) 在这个点开车的信息,如果向上跳 \(2^0\) 时,显然,我们走 \(A\) 的边。如果向上跳 \(2^1\) 时,我们需要由 \(B\) 开始的边。而之后,向上跳 \(2^t\) 时,我们都可以由之前得到的由 \(A\) 开始的边得到。这不难理解,即相当于我每个序列都是 \(ABAB……AB\) 交错,倍增扩展时,两个序列的开头都是 \(A\),只有 \(2^1\) 特殊,单独讨论即可。

建图的过程可以倒着由 \(set\) 维护,预处理的过程即倒序对每个点向上倍增,因为满足编号小的点深度一定不大于编号大的点,所以当遍历到编号小的点时,其父亲的倍增信息已经处理完毕。

\(code\)

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 10, INF = 1e18;
inline int read()
{
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w *= -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * w;
}
struct City{
	int v, id; //城市海拔,城市编号
	bool operator < (const City &x) const{ return v < x.v; } //按照海拔升序排列 
};
struct node{
	int d, v, id; //距离、海拔、编号
	bool operator < (const node &x) const{
		if(d == x.d) return v < x.v;
		else return d < x.d;
	}
}sck[5];
struct Edge{
	int v, w; //目标点,边权 
};
struct Distance{ int a, b; }; //a 走的路程,b 走的路程 
int n, t, Q, mx;
int h[N], dp[N][2];
int f[N][21][2], A[N][21][2], B[N][21][2];
bool vis[N];
//dp[u][0 / 1] 表示不考虑距离限制 x 的情况下,在城市 u,是 A/B 开车的移动距离 
set<City> s;
vector<Edge> G[N][2]; //当前点是 A/B 的连边情况
inline int gcd(int a, int b) { return !b ? a : gcd(b, a % b); }
inline void DFS(int u)
{
//	cerr << u << "\n";
	for(register Edge to : G[u][0]){ //A 开车的边 
		f[to.v][0][0] = u, A[to.v][0][0] = to.w;
		f[to.v][1][0] = f[f[to.v][0][0]][0][1]; //向上跳一格之后,就是 B 开车 
		A[to.v][1][0] = A[to.v][0][0], B[to.v][1][0] = B[f[to.v][0][0]][0][1];
		for(register int i = 2; i <= t; i++){ //发现倍增到 2 之后再倍增都是以 A 开头的段 
			f[to.v][i][0] = f[f[to.v][i - 1][0]][i - 1][0];
			A[to.v][i][0] = A[to.v][i - 1][0] + A[f[to.v][i - 1][0]][i - 1][0];
			B[to.v][i][0] = B[to.v][i - 1][0] + B[f[to.v][i - 1][0]][i - 1][0];
		}
	}
//	cerr << "finishA\n";
	for(register Edge to : G[u][1]){ //由 B 开车的边 
		f[to.v][0][1] = u, B[to.v][0][1] = to.w;
		f[to.v][1][1] = f[f[to.v][0][1]][0][0];
		A[to.v][1][1] = A[f[to.v][0][1]][0][0], B[to.v][1][1] = B[to.v][0][1];
		for(register int i = 2; i <= t; i++){
			f[to.v][i][1] = f[f[to.v][i - 1][1]][i - 1][1];
			A[to.v][i][1] = A[to.v][i - 1][1] + A[f[to.v][i - 1][1]][i - 1][1];
			B[to.v][i][1] = B[to.v][i - 1][1] + B[f[to.v][i - 1][1]][i - 1][1];
		}
	}
}
inline Distance move(int u, int dis, int opt) //倍增 
{
	for(register int i = t; i >= 0; i--){
		if(f[u][i][opt] && A[u][i][opt] + B[u][i][opt] <= dis){ //路程不超过 dis 
			int nex = opt;
			if(!i) nex ^= 1; //只走一步改变驾驶人 
			Distance res = move(f[u][i][opt], dis - A[u][i][opt] - B[u][i][opt], nex);
			res.a += A[u][i][opt], res.b += B[u][i][opt];
			return res; 
		}
	}
	return (Distance){0, 0}; //走不动 
}
signed main()
{
//	freopen("data9.in", "r", stdin);
//	freopen("data.out", "w", stdout);
	n = read(), t = 20;
	for(register int i = 1; i <= n; i++) h[i] = read();
	for(register int i = n; i >= 1; i--){
		City u = (City){h[i], i};
		if(s.size()){ //考虑此时 B 开车 
			//显然,距离 i 最近的两个城市是海拔距离其最近的两个城市
			//通过 set 与 lower_bound 实现这个过程 
			int top = 0, to, dis;
			auto res = s.lower_bound(u); //找到第一个比 h[i] 更高的海拔 
			if(res != s.end())
				sck[++top] = (node){abs(h[i] - (*res).v), (*res).v, (*res).id};
			if(res != s.begin())
				res--,  sck[++top] = (node){abs(h[i] - (*res).v), (*res).v, (*res).id};;
			sort(sck + 1, sck + top + 1);
			to = sck[1].id, dis = sck[1].d;
			G[to][1].push_back((Edge){i, dis});
		}
		if(s.size() > 1){ //考虑此时 A 开车 
			int top = 0, to, dis;
			auto res = s.lower_bound(u);
			if(res != s.end()){
				sck[++top] = (node){abs(h[i] - (*res).v), (*res).v, (*res).id}, res++;
				if(res != s.end()) sck[++top] = (node){abs(h[i] - (*res).v), (*res).v, (*res).id};
			}
			res = s.lower_bound(u);
			if(res != s.begin()){
				res--, sck[++top] = (node){abs(h[i] - (*res).v), (*res).v, (*res).id};
				if(res != s.begin())
					res--, sck[++top] = (node){abs(h[i] - (*res).v), (*res).v, (*res).id};
			}
			sort(sck + 1, sck + top + 1);
			to = sck[2].id, dis = sck[2].d;
			G[to][0].push_back((Edge){i, dis});
		}
		s.insert(u);
	}
	for(register int i = n; i >= 1; i--) DFS(i);
	mx = read();
	int id, hig = -INF;
	long double ans = INF;
	for(register int i = 1; i <= n; i++){
		Distance res = move(i, mx, (int)0);
		if(!res.b){
			if(INF == ans){
				if(h[i] > hig) hig = h[i], id = i;
			}
		}
		else{
			int g = gcd(res.a, res.b);
			res.a /= g, res.b /= g;
			long double c = (long double)res.a / (long double)res.b; 
			if(c < ans) ans = c, id = i, hig = h[i];
			else{
				if(c == ans && h[i] > hig) id = i, hig = h[i];
			}
		}
	}
	printf("%lld\n", id);
	Q = read();
	while(Q--){
		int x = read(), y = read();
		Distance res = move(x, y, (int)0);
		printf("%lld %lld\n", res.a, res.b); 
	}
	return 0;
}
posted @ 2022-05-26 16:34  ╰⋛⋋⊱๑落叶๑⊰⋌⋚╯  阅读(89)  评论(0编辑  收藏  举报