[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\)
小 \(\text{A}\) 和小 \(\text{B}\) 的駕駛風格不同,小 \(\text{B}\) 總是沿著前進方向選擇一個最近的城市作為目的地,而小 \(\text{A}\) 總是沿著前進方向選擇第二近的城市作為目的地(注意:本題中如果當前城市到兩個城市的距離相同,則認為離海拔低的那個城市更近)。如果其中任何一人無法按照自己的原則選擇目的城市,或者到達目的地會使行駛的總距離超出 \(x\) 公里,他們就會結束旅行。
在啟程之前,小 \(\text{A}\) 想知道兩個問題:
1、 對於一個給定的 \(x=x_0\),從哪一個城市出發,小 \(\text{A}\)
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;
}