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;
}