題解: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\)
對於這種情況:假設我們當前列舉到了 \(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}}\)
升序同理。
當然,最初
這樣我們就輕鬆的解決了這道題。
總結
其實也沒啥好總結的。 用單調棧優化 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;
}