(倍增)開車旅行
阿新 • • 發佈:2018-11-28
https://www.luogu.org/problemnew/show/P1081
小 A 和小 B 決定利用假期外出旅行,他們將想去的城市從 1到 N編號,且編號較小的城市在編號較大的城市的西邊,已知各個城市的海拔高度互不相同,記城市 i 的海拔高度為恰好是這兩個城市海拔高度之差的絕對值,即d(i, j) = abs(h[i] - h[j])
旅行過程中,小 A 和小 B輪流開車,第一天小 A 開車,之後每天輪換一次。他們計劃選擇一個城市 S作為起點,一直向東行駛,並且最多行駛 X 公里就結束旅行。小 A 和小 B的駕駛風格不同,小 B 總是沿著前進方向選擇一個最近的城市作為目的地,而小A總是沿著前進方向選擇第二近的城市作為目的地(注意:本題中如果當前城市到兩個城市的距離相同,則認為離海拔低的那個城市更近)。如果其中任何一人無法按照自己的原則選擇目的城市,或者到達目的地會使行駛的總距離超出 X公里,他們就會結束旅行。
在啟程之前,小A想知道兩個問題:
對於一個給定的 X=X0,從哪一個城市出發,小 A 開車行駛的路程總數與小 B 行駛的路程總數的比值最小(如果小 B 的行駛路程為 0,此時的比值可視為無窮大,且兩個無窮大視為相等)。如果從多個城市出發,小 A 開車行駛的路程總數與小B行駛的路程總數的比值都最小,則輸出海拔最高的那個城市。
對任意給定的 X=Xi和出發城市 Si ,小 A 開車行駛的路程總數以及小 B 行駛的路程總數。
在每個城市,小a開車會到第二近的城市,小b開車則到最近的城市,如果無法執行自己的要求或者要走的距離大於限制則會停止。在每個城市我們可以預處理出來它能到達的最近和第二近的城市,再使用倍增迴圈出每個點出發後開車的距離和終點
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e5 + 10; #define inf 2147483645 #define eps 0.000003 int n, h[maxn], s, x, m; int des[maxn][2], Min[maxn][2]; int to[maxn][22], dis[maxn][22][2]; int ans[2]; struct node { int h, id; node() {} node(int _h, int _id) : h(_h), id(_id) {} bool operator <(const node &a) const { return h < a.h; } }; set<node> mp; set<node>:: iterator it; //des[0]最近的點位置,des[1]第二近的點位置 //Min[x][0]以x為起點的最小海拔差,Min[x][1]---第二小海拔差,方便更新 void up(int u, node x) { int v = x.id; if((abs(h[u] - h[v]) < Min[u][0]) || (Min[u][0] == abs(h[u] - h[v]) && h[v] < h[des[u][0]])) { //更新最近點 if((Min[u][0] < Min[u][1]) || (Min[u][1] == Min[u][0] && h[des[u][0]] < h[des[u][1]])) //原來的最近點變成了第二近點 Min[u][1] = Min[u][0], des[u][1] = des[u][0]; Min[u][0] = abs(h[u] - h[v]), des[u][0] = v; } else if((abs(h[u] - h[v]) < Min[u][1]) || (Min[u][1] == abs(h[u] - h[v]) && h[v] < h[des[u][0]])) //注意可能能更新第二近點 Min[u][1] = abs(h[u] - h[v]), des[u][1] = v; } void judge(int xi, int si) { //判斷以xi為起點,si的距離限制a,b走多遠 for(int k = 20; k >= 0; k--) { if(dis[xi][k][0] + dis[xi][k][1] <= si && to[xi][k]) { si -= (dis[xi][k][0] + dis[xi][k][1]); ans[1] += dis[xi][k][1]; ans[0] += dis[xi][k][0]; xi = to[xi][k]; } } if(des[xi][1] && Min[xi][1] <= si) ans[1] += Min[xi][1]; //a,b分別開一次不滿足,可能a還能開一次 } int main() { scanf("%d", &n); register int i, j, k; for(i = 1; i <= n; i++) scanf("%d", &h[i]), Min[i][0] = Min[i][1] = inf; //預處理出每個點的最近兩點 for(i = n; i >= 1; i--) { mp.insert(node(h[i], i)); it = mp.find(node(h[i], i)); it++; if(it != mp.end()) { up(i, *it); it++; if(it != mp.end()) up(i, *it); it--; } it--; if(it != mp.begin()) { up(i, *(--it)); if(it != mp.begin()) up(i, *(--it)); } } //to記錄每一輪ab兩人輪換開車後的目的地 //dis記錄i起點,最近,第二近能到達的點的情況 for(i = 1; i <= n; i++) { to[i][0] = des[des[i][1]][0]; dis[i][0][1] = Min[i][1]; dis[i][0][0] = Min[des[i][1]][0]; } for(k = 1; k <= 20; k++) { for(i = 1; i <= n; i++) { to[i][k] = to[to[i][k - 1]][k - 1]; dis[i][k][1] = dis[i][k - 1][1] + dis[to[i][k - 1]][k - 1][1]; dis[i][k][0] = dis[i][k - 1][0] + dis[to[i][k - 1]][k - 1][0]; } } scanf("%d", &x); double rate = inf; int pos = 0; h[0] = -inf; for(i = 1; i <= n; i++) { ans[0] = ans[1] = 0; judge(i, x); double tmp = ans[0] ? 1.0 * ans[1] / ans[0] : inf; if(tmp - rate < eps && tmp - rate > -eps && h[i] > h[pos]) pos = i; if(rate - tmp > eps) pos = i, rate = tmp; } printf("%d\n", pos); scanf("%d", &m); while(m--) { scanf("%d%d", &s, &x); ans[0] = ans[1] = 0; judge(s, x); printf("%d %d\n", ans[1], ans[0]); } }