[luogu p1081] 开车旅行
\(\mathtt{Link}\)
\(\mathtt{Description}\)
此题为复杂细节题,无法总结题意,所以给出原题:
小 \(\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\) 行驶的路程总数。
\(\mathtt{Solution}\)
暴力
首先考虑暴力做法。
预处理出四个数组:A, B, disA, disB。
- \(A_i\) 表示车在第 \(i\) 个城市时,小A选择的目的地;
- \(B_i\) 表示车在第 \(i\) 个城市时,小B选择的目的地;
- \(disA_i\) 表示车从 \(i\) 到达小A选择目的地(即 \(A_i\))的距离;
- \(disB_i\) 表示车从 \(i\) 到达小B选择目的地(即 \(B_i\))的距离。
然后模拟行车过程即可。
代码就不给了,懒得写……
\(\mathtt{Time} \text{ } \mathtt{Complexity}\)
稍加思考便可知,预处理是 \(\mathcal{O}(n ^ 2)\) 的。
然后再模拟行车过程,复杂度 \(\mathcal{O}(nm)\)。
观察数据范围,暴力做法能得到 \(70 \mathtt{pts}\),分数非常可观。
倍增
接下来就是正菜了,我们考虑在暴力的基础上优化一下。
观察到每次A和B的目标点在位置确定时就已经确定了,因此考虑倍增。
distotA[i][j] 表示汽车从i,被A和B轮流分别开了 1 << j 次后 A 的总路程
distotB[i][j] 表示汽车从i,被A和B轮流分别开了 1 << j 次后 B 的总路程
curpos[i][j] 表示汽车从i,被A和B轮流分别开了 1 << j 次后汽车的位置
先考虑 \(j=0\) 的情况:
for (int i = 1; i <= n; ++i) {
distotA[i][0] = disA[i];
distotB[i][0] = disB[A[i]];
curpos[i][0] = B[A[i]];
}
这个还是很好理解的吧!
再来看看 \(j \ge 1\) 的情况:
for (int j = 1; j <= maxlogn - 5; ++j)
for (int i = 1; i <= n; ++i) {
curpos[i][j] = curpos[curpos[i][j - 1]][j - 1];
if (curpos[i][j]) {
distotA[i][j] = distotA[i][j - 1] + distotA[curpos[i][j - 1]][j - 1];
distotB[i][j] = distotB[i][j - 1] + distotB[curpos[i][j - 1]][j - 1];
}
}
那么倍增我们就优化完了,一个 \(n\) 掉成了 \(\log n\)。但是这还远远不够。
disA和disB的处理仍然是平方级别的。怎么办呢?我们使用双向链表来把这里的预处理降低成 \(\mathcal{O}(n)\) 级别。
首先,我们把这 \(n\) 个城市按照高度排序。(好吧,这已经 \(n \log n\) 了)
排序后,那么一个城市 \(i\) 的最小距离点和次小距离点,一定就在 \(i-2\), \(i-1\), \(i+1\), \(i+2\) 这四个位置上。
那么第一个找点的显然就是一号城市。然后你就会惊奇的发现,一号城市就是最西边了,也就是说找到的任何城市都在一号城市东边。这大大方便我们直接计算disA和disB的值。
找到之后,炸了一号城市,开始处理二号城市。嗯因为一号城市已经被炸了,同样所有城市都在二号城市东边,太方便啦!
处理完一个炸一个,直到最后炸没了,整个disA和disB就求解完毕了。
至于为什么用双向链表嘛,这是因为炸城市需要链表维护,然后我们又需要找一个城市的前驱和后继,嗯,妥妥双向链表。
\(\mathtt{Time} \text{ } \mathtt{Complexity}\)
- 排序 \(\mathcal{O}(n \log n)\)
- 双向链表处理disA和disB \(\mathcal{O}(n)\)
- 倍增处理distotA和distotB \(\mathcal{O}(n\log n)\)
- 解决第一小问 \(\mathcal{O}(n\log n)\)
- 解决第二小问 \(\mathcal{O}(m\log n)\)
整体就是 \(\mathcal{O}(n\log n)\) 这个级别的,显然这个算法可以通过此题。
那就上代码吧!
\(\mathtt{Code}\)
/*
* @Author: crab-in-the-northeast
* @Date: 2020-11-20 23:36:45
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2020-11-21 01:00:02
*/
#include <bits/stdc++.h>
const int maxn = 100005;
const int maxlogn = 25;
const double eps = 0.0000001;
inline long long read() {
long long x = 0;
bool f = true;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
f = false;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + ch - '0';
ch = getchar();
}
if (f)
return x;
return ~(x - 1);
}
int n;
long long A[maxn], B[maxn], disA[maxn], disB[maxn], distotA[maxn][maxlogn], distotB[maxn][maxlogn], curpos[maxn][maxlogn];
struct building {
long long h;
int num, lst, nxt;
const bool operator < (const building& b) {
return this -> h < b.h;
}
}a[maxn];
int p[maxn];
inline void upddis(int spos, int s, int t) {
if (t < 1 || t > n)
return ;
int dis = std :: abs(a[s].h - a[t].h);
if (disB[spos] == 0 || disB[spos] > dis || (disB[spos] == dis && a[t] < a[p[B[spos]]])) {
disA[spos] = disB[spos];
disB[spos] = dis;
A[spos] = B[spos];
B[spos] = a[t].num;
} else if (disA[spos] == 0 || disA[spos] > dis || (disA[spos] == dis && a[t] < a[p[A[spos]]])) {
disA[spos] = dis;
A[spos] = a[t].num;
}
return ;
}
int main() {
n = read();
for (int i = 1; i <= n; ++i) {
a[i].num = i;
a[i].h = read();
}
std :: sort(a + 1, a + 1 + n);
for (int i = 1; i <= n; ++i) {
if (i != 1)
a[i].lst = i - 1;
if (i != n)
a[i].nxt = i + 1;
p[a[i].num] = i;
}
// solve disA, disB On
for (int i = 1; i <= n; ++i) {
int pos = p[i];
upddis(i, pos, a[a[pos].lst].lst);
upddis(i, pos, a[pos].lst);
upddis(i, pos, a[pos].nxt);
upddis(i, pos, a[a[pos].nxt].nxt);
if (a[pos].lst)
a[a[pos].lst].nxt = a[pos].nxt;
if (a[pos].nxt)
a[a[pos].nxt].lst = a[pos].lst;
a[pos].lst = a[pos].nxt = 0;
}
//puts("fff");
// solve distotA, distotB, Onlogn
for (int i = 1; i <= n; ++i) {
distotA[i][0] = disA[i];
distotB[i][0] = disB[A[i]];
curpos[i][0] = B[A[i]];
}
for (int j = 1; j <= maxlogn - 5; ++j)
for (int i = 1; i <= n; ++i) {
curpos[i][j] = curpos[curpos[i][j - 1]][j - 1];
if (curpos[i][j]) {
distotA[i][j] = distotA[i][j - 1] + distotA[curpos[i][j - 1]][j - 1];
distotB[i][j] = distotB[i][j - 1] + distotB[curpos[i][j - 1]][j - 1];
}
}
// solve Part1 nlogn
long long x0 = read(), ans = 0;
double minrat = INT_MAX;
for (int i = 1; i <= n; ++i) {
long long ansA = 0, ansB = 0, pos = i, tmpx = x0;
for (int j = maxlogn - 5; j >= 0; --j) {
if (distotA[pos][j] + distotB[pos][j] && tmpx >= distotA[pos][j] + distotB[pos][j]) {
tmpx -= distotA[pos][j] + distotB[pos][j];
ansA += distotA[pos][j];
ansB += distotB[pos][j];
pos = curpos[pos][j];
}
}
if (disA[pos] <= tmpx)
ansA += disA[pos];
if (ansA == 0)
continue;
if (ans == 0 || minrat - 1.0 * ansA / ansB > eps || (fabs(minrat - 1.0 * ansA / ansB) <= eps && a[p[ans]] < a[p[i]])) {
minrat = 1.0 * ansA / ansB;
ans = i;
}
}
std :: printf("%lld\n", ans);
// solve Part2 mlogn
int m = read();
while (m--) {
long long s = read(), x = read(), ansA = 0, ansB = 0;
for (int j = maxlogn - 5; j >= 0; --j) {
if (distotA[s][j] + distotB[s][j] && x >= distotA[s][j] + distotB[s][j]) {
x -= distotA[s][j] + distotB[s][j];
ansA += distotA[s][j];
ansB += distotB[s][j];
s = curpos[s][j];
}
}
if (disA[s] <= x)
ansA += disA[s];
std :: printf("%lld %lld\n", ansA, ansB);
}
return 0;
}
\(\mathtt{More}\)
如果对于一个点,选择具有唯一性(或者说跳到哪里的选择只和位置有关),那么就可以考虑倍增优化。
附一个白话倍增(经 典 作 品)