1. 程式人生 > 其它 >題解:CF1407D Discrete Centrifugal Jumps

題解:CF1407D Discrete Centrifugal Jumps

題意簡析

  • 給你 \(n\) 座樓,沒座樓的高度為 \(h_i\)
  • 初始在第一座樓,你需要跳到第 \(n\) 座樓上。
  • 能從 \(i\) 跳到 \(j\) 需滿足以下條件之一:

\(1. i = j + 1\)

\(2. \min(h_i,h_j) > \max(h_{i+1},h_{i+2},...,h_{j-2},h_{j-1})\)

\(3. \max(h_i,h_j) < \min(h_{i+1},h_{i+2},...,h_{j-2},h_{j-1})\)

  • 問你最小需要跳幾次?
  • \(2\le n \le 3\times 10^5\),\(1\le h_i \le 10^9\)

分析

顯然,這是一道 dp 題。但是直接暴力列舉 \(i,j\),和列舉區間最大值和最小值,複雜度為 \(O(n^3)\)。可能某些神仙能把它優化到 \(O(n^2)\),但是本題仍不能過。考慮 \(O(n\log n)\) 的演算法。

這裡考慮單調棧。什麼是單調棧?

單調棧,是指一個棧中,所有的元素均滿足一種單調性。它可以是升序,也可以是降序,只要滿足單調皆可。

在這裡,區間的最大值,最小值,都可以存到一個單調棧中,每次需要查詢或更新時,將棧中的元素取出即可。這一複雜度為 \(O(\log n)\)就很神仙。

那麼在本題中,我們可以開兩個棧,分別為升序和降序,表示題意中最大值和最小值能跳躍的條件。方便規定,我們將棧頂元素最大表示為降序。記 \(s\)

表示降序的單調棧,且 \(s\) 中儲存的元素是它在 \(h\) 中的編號。

對於這種情況:假設我們當前列舉到了 \(i\),讓 \(h_i\) 與棧頂 \(h_{s_{tot}}\) 進行比較。如果 \(h_i\le h_{s_{tot}}\),違背了單調性,需要讓 \(tot=tot-1\),但是在這種情況,滿足我們跳躍的第二種條件。因為單調性,所以 \(h_{s_{tot-1}}\le h_{s_{tot}}\),且 \(h_{s_{tot}}\)\([tot,i]\) 中的最大值,又已知 \(h_i\le h_{s_{tot}}\),只要\(h_i\not = h_{s_{tot}}\)

肯定滿足 \(\min(h_i,h_{s_{tot-1}})<\max(h_{s_{tot}},...,h_{i-1})\),固有:

\[dp_i=\min(dp_i,dp_{s_{tot-1}}+1) \]

升序同理。
當然,最初

\[dp_i=dp_{i-1}+1 \]

這樣我們就輕鬆的解決了這道題。

總結

其實也沒啥好總結的。 用單調棧優化 dp。

#include <bits/stdc++.h>

using namespace std;
const int N = 3e5 + 7;;
int s[N], dp[N], a[N], b[N];
int tot1, tot2;

inline int max(int x, int y){return x > y ? x : y;}
inline int min(int x, int y){return x < y ? x : y;}

inline int read(){
	int x = 0, f = 1; char c = getchar();
	while (!isdigit(c)){if (c == '-') f = -1; c = getchar();}
	while (isdigit(c)){x = (x << 3) + (x << 1) + c - '0'; c = getchar();}
	return x * f;
}

int main(){
	int n = read();
	for (int i = 1; i <= n; i ++) s[i] = read();
	memset(dp, 0x3f3f3f3f, sizeof(dp));
	a[++ tot1] = 1, b[++ tot2] = 1, dp[1] = 0;
	for (int i = 2; i <= n; i ++){
		dp[i] = dp[i - 1] + 1;
		while (tot1 && s[i] >= s[a[tot1]]){
			if (s[i] != s[a[tot1]]) dp[i] = min(dp[i], dp[a[tot1 - 1]] + 1);
			tot1 --;
		}
		while (tot2 && s[i] <= s[b[tot2]]){
			if (s[i] != s[b[tot2]]) dp[i] = min(dp[i], dp[b[tot2 - 1]] + 1);
			tot2 --;
		} 
		a[++ tot1] = i, b[++ tot2] = i;
	}
	printf("%d", dp[n]);
	return 0;
}