题目来源
https://codeforces.ml/contest/1407/problem/D
题意分析
题目意思的大致可以描述为,给一个序列,长度为n,每一次操作,我们可以向该点后面的点进行跳跃,跳跃的点的要求满足三条中的某一条条件即可。(设跳跃起点为i,跳跃终点为j,他们的高度放在a数组中)
1. j = i + 1;
2. a[i]和a[j]的值小于所有的i和j之间的值(不包括i,j),即max(a[i], a[j])< min(a[i+1], a[i+1], ... , a[j-1]);
3. a[i]和a[j]的值大于所有的i和j之间的值(不包括i,j),即min(a[i], a[j])> max(a[i+1], a[i+1], ... , a[j-1]);
最后到达第n个点的最小操作次数。
思路分析
当时最后还是没有把这题写出来。菜。。。记录一下当时的想法。
看到这道题,我们可以将点大致的绘制在图上,很容易发现,当两个点位于一个山峰(上坡+下坡)或者是一个山谷(下坡+上坡)的两端时,那么这两个点就可以进行直接的跳跃。
第一想法是dp,因为很容易想到的是,到达这个点的操作次数的最小值,等于这个点前面的能到达这个点的最小操作次数 + 1。然后就死在了不知道如何在时间复杂度内判断能直接到达这个点的点是哪些。这个问题最后是使用单调栈去解决的,因为单调栈可以为我们维护一个以某个值为最大(最小)值的最大区间。这里同时使用一个单调递增栈和一个单调递减栈。在单调递增栈(栈顶到栈底单调递增)中,我们每次遇到一个比栈顶元素的小的时候,就直接将这个数入栈,而当我们遇到一个比栈顶元素大或者相等的数的时候,我们看此时的栈顶元素是否比我们此时的数要小,如果小的话,说明此时栈顶这个点到我们此时遍历到的这个数的这个区间内所有数都小于我们两端,所以就可以从这个点直接跳跃过去。反复进行这个操作,直至遍历结束。单调递减栈相同。
当然,如果我们能找到点之间直接跳跃的关系,我们还可以建边,走最短路。所以最大问题,或许在于建边把。
code
1 #include <bits/stdc++.h>
2
3 #define int long long
4 using namespace std;
5 const int maxn = 3e5 + 7;
6
7 int dp[maxn], a[maxn];
8
9 signed main(){
10 // freopen("test.txt", "r", stdin);
11 int n; scanf("%lld", &n);
12 for (int i=1; i<=n; i++)
13 scanf("%lld", &a[i]);
14
15 stack <int> u, d;
16 u.push(1); d.push(1);
17
18 for (int i=1; i<=n; i++) dp[i] = i - 1;
19
20 for (int i=2; i<=n; i++){
21 dp[i] = dp[i-1] + 1;
22
23 while (!u.empty()){
24 // printf("! u\n");
25 int x = a[u.top()];
26 if (x < a[i]) break;
27
28 u.pop();
29 if (u.empty()) break;
30 if (a[i] < x)
31 dp[i] = min(dp[i], dp[u.top()] + 1);
32
33 }
34 u.push(i);
35
36 while (!d.empty()){
37 // printf("! d\n");
38 int x = a[d.top()];
39
40 if (x > a[i]) break;
41 d.pop();
42 if (d.empty()) break;
43
44 if (x < a[i])
45 dp[i] = min(dp[i], dp[d.top()] + 1);
46 }
47 d.push(i);
48 // for (int j=1; j<=n; j++) printf("%lld ", dp[j]);
49 // printf("\n");
50 }
51
52 // for (int i=1; i<=n; i++) printf("%lld ", dp[i]);
53 // printf("\n");
54
55 printf("%lld\n", dp[n]);
56
57 return 0;
58 }