32 noip2012 开车旅行 题解

noip2012 开车旅行

题外话:这题其实没那么难,我刚开始一直以为 A 走最小值, B 走次小值,调了一上午

题面

给定 \(n\) 个山,第 \(i\) 个高度为 \(h_i\) ,从 i 到 j 距离为 \(\lvert h_i-h_j\rvert\) (如果当前城市到两个城市的距离相同,则认为离海拔低的那个城市更近)。

两人选定从第 \(s\) 个山出发,向右走,两个人轮流开车,A先开

  • A 每次选择右侧山中距离 次小 的,B 每次选择右侧山中距离 最小
  • 任何一人无法开车了,或者总路程超过 \(x\) 了,就停止

问题1:对某个固定的 \(x\) 选哪个城市出发,最小化 \(\frac {A路程}{B路程}\) (如果小 B 的行驶路程为 0,此时的比值可视为无穷大,且两个无穷大视为相等。如果比值相同,则输出海拔最高的那个城市),问题1只有一个

问题2:给定 \(s,x\) 求两个人的路程分别是多少

\(1 \le n,q \le 10^5,0 \le |h_i|,x \le 10^9\)

题解

给定起点和距离的话,其实路径是唯一确定的,所以我们一步一步走和很多步一起走是等价的

这道题整体的大思路是倍增去优化我们走的过程,我们可以枚举走了 \(2^j\) 步,然后看看是否超过了距离

转移的话,我们首先要求出走 \(2^j\) 步后的距离是多少,还要算出走 \(2^j\) 步后走到了哪个点

因为两个人分别要走最近点和次近点,所以我们可以先预处理出每个点向右的最近点和次近点,这个可以用链表/set来做

然后用倍增,求出下面几个数组

  • \(f(i,j,0/1)\) 从 i 开始,A/B先开车,走 \(2^j\) 步到达的点
  • \(da(i,j,0/1)\) 从 i 开始,A/B先开车,走 \(2^j\) 步 A 走过的路程
  • \(db(i,j,0/1)\) 从 i 开始,A/B先开车,走 \(2^j\) 步 B 走过的路程

预处理这几个数组都是用倍增去搞的,都是 \(O(n \log n)\)

然后我们就可以用倍增求解,因为问题1只有一个,所以可以当成 n 个问题2来做,总时间复杂度 \(O(n\log n)\)

code

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <vector>
#include <set>

using namespace std;

typedef long long ll;
typedef pair <ll, int> pli;

const int N = 1e5 + 10;
const ll inf = 1e15;

int n, m;
int h[N], ga[N], gb[N];
ll da[N][20][2], db[N][20][2];
int f[N][20][2];

void init_g () {
	set <pli> s;
	s.insert ({inf, 0}), s.insert ({inf + 1, 0});
	s.insert ({-inf, 0}), s.insert ({-inf - 1, 0});
	for (int i = n; i >= 1; i--) {
		pli t(h[i], i);
		auto j = s.lower_bound (t);
		j++;
		vector <pli> cand;
		for (int k = 0; k < 4; k++) {
			cand.push_back (*j);
			j--;
		}
		ll d1 = inf, d2 = inf;
		int p1 = 0, p2 = 0;
		for (int k = 3; k >= 0; k--) {
			ll d = abs (h[i] - cand[k].first);
			if (d < d1) {
				d2 = d1, d1 = d;
				p2 = p1, p1 = cand[k].second;
			} else if (d < d2) {
				d2 = d;
				p2 = cand[k].second;
			}
		}
		ga[i] = p2, gb[i] = p1;
		s.insert (t);
	}
}

void init_f () {
	for (int i = 1; i <= n; i++) {
		f[i][0][0] = ga[i];
		f[i][0][1] = gb[i];
	}
	for (int j = 1; j <= 17; j++) {
		for (int i = 1; i <= n; i++) {
			for (int k = 0; k <= 1; k++) {
				if (j == 1) f[i][j][k] = f[f[i][j - 1][k]][j - 1][1 - k];
				else f[i][j][k] = f[f[i][j - 1][k]][j - 1][k];
			}
		}
	}
}

void init_d () {
	for (int i = 1; i <= n; i++) {
		da[i][0][0] = abs (h[i] - h[ga[i]]);
		da[i][0][1] = 0;
		db[i][0][0] = 0;
		db[i][0][1] = abs (h[i] - h[gb[i]]);
	}
	for (int j = 1; j <= 17; j++) {
		for (int i = 1; i <= n; i++) {
			for (int k = 0; k <= 1; k++) {
				if (j == 1) {
					da[i][j][k] = da[i][j - 1][k] + da[f[i][j - 1][k]][j - 1][1 - k];
					db[i][j][k] = db[i][j - 1][k] + db[f[i][j - 1][k]][j - 1][1 - k];
				} else {
					da[i][j][k] = da[i][j - 1][k] + da[f[i][j - 1][k]][j - 1][k];
					db[i][j][k] = db[i][j - 1][k] + db[f[i][j - 1][k]][j - 1][k];
				}
			}
		}
	}
}

void calc (int s, int x, ll &la, ll &lb) {
	la = lb = 0;
	for (int i = 17; i >= 0; i--) {
		if (f[s][i][0] && la + lb + da[s][i][0] + db[s][i][0] <= x) {
			la += da[s][i][0], lb += db[s][i][0];
			s = f[s][i][0];
		}
	}
}


int main () {
	scanf ("%d", &n);
	for (int i = 1; i <= n; i++) scanf ("%d", &h[i]);
	init_g ();
	init_f ();
	init_d ();

	int s, x;
	scanf ("%d", &x);
	int mah = 0, res = 0;
	double min_ratio = inf;
	for (int i = 1; i <= n; i++) {
		ll la, lb;
		calc (i, x, la, lb);
		double ratio = lb ? (double) la / lb : inf;
		if (ratio < min_ratio || (ratio == min_ratio && h[i] > mah)) {
			min_ratio = ratio;
			mah = h[i];
			res = i;
		}
	}
	printf ("%d\n", res);

	scanf ("%d", &m);
	while (m --) {
		scanf ("%d%d", &s, &x);
		ll la, lb;
		calc (s, x, la, lb);
		printf ("%lld %lld\n", la, lb);

	}


	return 0;
}
posted @ 2025-10-09 21:19  michaele  阅读(3)  评论(0)    收藏  举报