1. 程式人生 > 其它 >[NOIP2012提高組]開車旅行[題解]

[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\)

作為起點,一直向東行駛,並且最多行駛 \(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\) 行駛的路程總數。

資料範圍與約定

對於 \(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;
}