单调队列/单调栈优化DP
滑动窗口、单调队列、单调栈、DP优化
从这几篇博客学习的:DP优化小技巧(单调队列/单调栈) (单调队列优化DP) 代码源每日一题 Div1 选元素(数据加强版) 算法学习笔记(67): 单调栈 牛客多校第九场I (单调栈优化dp/单调栈的常用套路)
一. 单调队列
主要思想: 假设我们需要维护长度为$k$的区间最大值,遍历过程中,对于一个数字$a_{i+1}$,如果$a_{i+1} >= a_i$,那么我们完全可以把$a_i$的影响忽略掉。因为后面的数字比你并且生命周期还比你大,所以最大值永远不可能取到$a_i$。具体在队列中的做法就是不断访问队尾元素,小于等于当前值就出队,结束后将当前元素入队。这样的话就可以保证队首元素一定是长度为$k$的区间的最大值。(当然你需要判断队首元素与当前位置距离与$k$的大小关系确定队首元素是否需要出队)。这个过程可以用deque很容易实现,数组模拟也很容易。AC代码: deque:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <bits/stdc++.h> using namespace std;int n, k;int ar[1000050 ];deque<int > q; int main () { scanf ("%d%d" , &n, &k); for (int i = 1 ; i <= n; ++i) scanf ("%d" , &ar[i]); for (int i = 1 ; i <= n; ++i) { while (!q.empty () && q.front () + k <= i) q.pop_front (); while (!q.empty () && ar[q.back ()] >= ar[i]) q.pop_back (); q.push_back (i); if (i >= k) printf ("%d " , ar[q.front ()]); } putchar ('\n' ); q.clear (); for (int i = 1 ; i <= n; ++i) { while (!q.empty () && q.front () + k <= i) q.pop_front (); while (!q.empty () && ar[q.back ()] <= ar[i]) q.pop_back (); q.push_back (i); if (i >= k) printf ("%d " , ar[q.front ()]); } return 0 ; }
数组模拟:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <bits/stdc++.h> using namespace std;int n, k;int ar[1000050 ];int p[1000050 ];int l, r;int main () { scanf ("%d%d" , &n, &k); for (int i = 1 ; i <= n; ++i) scanf ("%d" , &ar[i]); l = 0 ; r = 1 ; p[0 ] = 1 ; if (k == 1 ) printf ("%d " , ar[1 ]); for (int i = 2 ; i <= n; ++i) { if (i - p[l] >= k && (l < r)) l++; while (r > l && ar[p[r - 1 ]] >= ar[i]) r--; p[r++] = i; if (i >= k) printf ("%d " , ar[p[l]]); } putchar ('\n' ); l = 0 ; r = 1 ; p[0 ] = 1 ; if (k == 1 ) printf ("%d " , ar[1 ]); for (int i = 2 ; i <= n; ++i) { if (i - p[l] >= k && (l < r)) l++; while (r > l && ar[p[r - 1 ]] <= ar[i]) r--; p[r++] = i; if (i >= k) printf ("%d " , ar[p[l]]); } putchar ('\n' ); return 0 ; }
二. 单调队列优化dp 当我们为了实现给动态规划的复杂度降维 的时候,通常就需要单调栈/队列,通常用来维护前面状态 下可以取到的最大值或者最小值 ,然后直接进行转移。(ygg)
首先,我们可以看数据没加强版
题意: 就是对与每个长度为为$k$的子区间,至少要有一个数被选择,一共可以选择$x$个数字,目标是让选择的$x$个数字最大。分析: 考虑dp,定义dp[i][j]
表示选了ar[i]
的情况下,前$i$个数字一共有$j$个被选的情况下的最大值。 状态转移方程:$dp[i][j] =max(dp[i][j],dp[i-k,i-1][j-1]+ar[i])$。复杂度$O(n^3)$。AC代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <bits/stdc++.h> using namespace std;typedef long long ll;#define int ll int n, k, x;int ar[205 ];int dp[205 ][205 ];int ans;signed main () { scanf ("%lld%lld%lld" , &n, &k, &x); for (int i = 1 ; i <= n; ++i) scanf ("%lld" , &ar[i]); memset (dp, 128 , sizeof (dp)); dp[0 ][0 ] = 0 ; for (int i = 1 ; i <= n; ++i) { for (int j = 1 ; j <= x; ++j) { for (int p = max (0ll , i - k); p < i; ++p) { dp[i][j] = max (dp[i][j], dp[p][j - 1 ] + ar[i]); } } } ans = -1 ; for (int i = n - k + 1 ; i <= n; ++i) ans = max (ans, dp[i][x]); printf ("%lld\n" , ans); return 0 ; }
数据加强版 对于数据加强版,$n,k,x$的取值达到2500,$O(n^3)$一定超时。我们考虑优化,对于原来的状态转移式。$dp[i][j] =max(dp[i][j],dp[i-k,i-1][j-1]+ar[i])$,考虑以极快的速度求出$max(dp[i-k,i-1][j-1])$。本质上一个窗口大小为$k$的滑动窗口,故利用单调队列优化即可。复杂度$O(n^2)$。AC代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <bits/stdc++.h> using namespace std;typedef long long ll;int n, k, x, top;ll ar[2550 ]; ll dp[2550 ][2550 ]; ll ans; int main () { scanf ("%d%d%d" , &n, &k, &x); for (int i = 1 ; i <= n; ++i) scanf ("%lld" , &ar[i]); memset (dp, 128 , sizeof (dp)); dp[0 ][0 ] = 0 ; for (int j = 1 ; j <= x; ++j) { deque<int > q; q.push_back (0 ); for (int i = 1 ; i <= n; ++i) { while (!q.empty () && q.front () < i - k) q.pop_front (); dp[i][j] = dp[q.front ()][j - 1 ] + ar[i]; while (!q.empty () && dp[q.back ()][j - 1 ] <= dp[i][j - 1 ]) q.pop_back (); q.push_back (i); } } ans = -1 ; for (int i = n - k + 1 ; i <= n; ++i) ans = max (ans, dp[i][x]); printf ("%lld\n" , ans); return 0 ; }
题意: 输入$n,q,p$,之后一行$n$个数字,之后$q$行,$q$次询问。 你现在在玩一个游戏,初始在0点,你只可以向右走,游戏有很多关,对于第$x$关,你只能走到$i$的倍数的点上,并且每步跨越的最大距离是$p$,每个点上有数字,当你到达这个点是,你的数值就会加上该点的权值,初始时你的数值为0,通关条件是到达点$n+1$及其之后的点,并且$n+1$及其之后的点的数值都为0,走到$n+1$之后的点不需要考虑他们是否是$x$的倍数,只需要考虑最大跨越距离的限制。你可以在不违规的前提下走任意多步,$q$次询问,问你对于第$x$关,通关时的最大数值。分析: 考虑dp,定义dp[i]
表示,对于第$x$关,走到第$i$个能到达的点时所能获得的最大价值。 转移方程:$dp[i] = max(dp[i], dp[i-p/x,i-1]+ar[i])$ (不考虑Noob的情况)。 如果暴力跑,复杂度为$O(TLE)$,依旧考虑单调队列优化取$max$的部分。复杂度$O(nlogn)$。AC代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include <bits/stdc++.h> using namespace std;typedef long long ll;#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) const ll inf = 0x3f3f3f3f3f3f3f3f ;int n, p, q, x;ll ans[1000050 ]; int ar[1000050 ];ll dp[1000050 ]; vector<int > vt[1000005 ]; int qq[1000050 ];int l, r;int main () { io; cin >> n >> q >> p; for (int i = 1 ; i <= n; ++i) cin >> ar[i]; for (int i = 1 ; i <= n; ++i) { ans[i] = -inf; for (int j = 0 ; j <= n; j += i) vt[i].push_back (j); vt[i].push_back (n + 1 ); } while (q--) { cin >> x; if (ans[x] != -inf) cout << ans[x] << '\n' ; else if (x > p) cout << "Noob\n" ; else { dp[0 ] = 0 ; l = r = 1 ; qq[r] = 0 ; for (int i = 1 ; i < vt[x].size (); ++i) { while (l <= r && vt[x][i] - qq[l] > p) ++l; dp[vt[x][i]] = dp[qq[l]] + ar[vt[x][i]]; while (l <= r && dp[qq[r]] <= dp[vt[x][i]]) --r; qq[++r] = vt[x][i]; } ans[x] = dp[n + 1 ]; cout << ans[x] << '\n' ; } } return 0 ; }
题意: 很类似上一个题,只不过跳的方式有所不同,是给定一个$l,r$,对于当前点$i$,可以跳到$[i+l,i+r]$AC代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <bits/stdc++.h> using namespace std;const int inf = 0x3f3f3f3f ;int n, l, r;int ar[600050 ];int dp[600050 ];deque<int > q; int main () { scanf ("%d%d%d" , &n, &l, &r); for (int i = 0 ; i <= n; ++i) { scanf ("%d" , &ar[i]); dp[i] = -inf; } dp[0 ] = ar[0 ]; q.push_back (0 ); dp[l] = ar[l]; for (int i = l + 1 ; i <= 3 * n; ++i) { while (!q.empty () && q.front () + r < i) q.pop_front (); while (!q.empty () && dp[q.back ()] <= dp[i - l]) q.pop_back (); q.push_back (i - l); dp[i] = dp[q.front ()] + ar[i]; } printf ("%d\n" , dp[3 * n]); return 0 ; }
分析: dp[i]
表示以i为结尾的一段长度不超过m的连续子序列的和的最大。 $dp[i] = \max_{j=i-m}^{j=i-1} sum[i]-sum[j]$ 单调队列维护长度为m的区间中-sum[j]
的最大值(或者sum[j]
的最小值)即可$O(1)$转移,总体复杂度$O(n)$。AC代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include <bits/stdc++.h> using namespace std;typedef long long ll;#define int ll #define endl '\n' #define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) const int maxn = 3e5 + 5 ;const int inf = 0x3f3f3f3f ;int n, m;int ar[maxn];int sum[maxn];int dp[maxn];deque<pair<int ,int >> q; bool flag;void print () { int ans = -inf; for (int i = 1 ; i <= n; ++i) ans = max (ans, ar[i]); cout << ans << '\n' ; } signed main () { cin >> n >> m; flag = false ; for (int i = 1 ; i <= n; ++i) { cin >> ar[i]; sum[i] = sum[i - 1 ] + ar[i]; if (ar[i] > 0 ) flag = true ; } if (!flag) print (); else { deque<int > q; q.push_back (0 ); for (int i = 1 ; i <= n; ++i) { while (!q.empty () && i - q.front () > m) q.pop_front (); dp[i] = sum[i] - sum[q.front ()]; while (!q.empty () && -sum[i] >= -sum[q.back ()]) q.pop_back (); q.push_back (i); } int mx = -inf; for (int i = 1 ; i <= n; ++i) mx = max (mx, dp[i]); cout << mx << '\n' ; } return 0 ; }
分析: dp[i]
表示前i头牛选第i头牛的最大效率 $dp[i] = \max_{j = i-k}^{j = i-1} dp[j - 1] + sum[i] - sum[j]$ 单调队列维护dp[j-1]-sum[j]
的最大值即可。AC代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <bits/stdc++.h> using namespace std;typedef long long ll;#define int ll #define endl '\n' #define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) const int maxn = 3e5 + 5 ;const int inf = 0x3f3f3f3f ;int n, k;int ar[maxn];int sum[maxn];int dp[maxn];int gg[maxn];signed main () { io; cin >> n >> k; for (int i = 1 ; i <= n; ++i) { cin >> ar[i]; sum[i] = sum[i - 1 ] + ar[i]; } deque<int > q; q.push_back (0 ); gg[0 ] = 0 ; for (int i = 1 ; i <= n; ++i) { while (!q.empty () && i - q.front () > k) q.pop_front (); dp[i] = sum[i] + gg[q.front ()]; gg[i] = -sum[i] + dp[i - 1 ]; while (!q.empty () && gg[i] >= gg[q.back ()]) q.pop_back (); q.push_back (i); } int mx = -inf; for (int i = 1 ; i <= n; ++i) mx = max (mx, dp[i]); cout << mx << '\n' ; return 0 ; }
分析: 二分答案+单调队列优化dpAC代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <bits/stdc++.h> using namespace std;typedef long long ll;#define int ll #define endl '\n' #define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) const int maxn = 2e5 + 5 ;const int inf = 0x3f3f3f3f ;int n, m;int ar[maxn];int dp[maxn];bool judge (int k) { deque<int > q; q.push_back (0 ); dp[0 ] = 0 ; for (int i = 1 ; i <= n; ++i) { while (!q.empty () && i - q.front () > k) q.pop_front (); dp[i] = dp[q.front ()] + ar[i]; while (!q.empty () && dp[i] <= dp[q.back ()]) q.pop_back (); q.push_back (i); } int mi = inf; for (int i = n; i > n - k; --i) mi = min (mi, dp[i]); return mi <= m; } signed main () { io; cin >> n >> m; for (int i = 1 ; i <= n; ++i) cin >> ar[i]; int l = 0 , r = n; while (l <= r) { int mid = (l + r) >> 1 ; if (judge (mid)) r = mid - 1 ; else l = mid + 1 ; } cout << r << '\n' ; return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <bits/stdc++.h> using namespace std;typedef long long ll;#define int ll #define endl '\n' #define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) const int maxn = 5e4 + 5 ;const int inf = 0x3f3f3f3f ;int n, t;int ar[maxn];int sum[maxn];int gg[maxn];int dp[maxn];bool judge (int mid) { deque<int > q; memset (dp, 0 , sizeof (dp)); q.push_back (0 ); gg[0 ] = 0 ; for (int i = 1 ; i <= n; ++i) { while (!q.empty () && i - q.front () > mid) q.pop_front (); dp[i] = sum[i] + gg[q.front ()]; gg[i] = -sum[i] + dp[i - 1 ]; while (!q.empty () && gg[i] >= gg[q.back ()]) q.pop_back (); q.push_back (i); } int mx = -inf; for (int i = 1 ; i <= n; ++i) mx = max (mx, dp[i]); if (sum[n] - mx > t) return false ; else return true ; } signed main () { io; cin >> n >> t; for (int i = 1 ; i <= n; ++i) { cin >> ar[i]; sum[i] = sum[i - 1 ] + ar[i]; } if (sum[n] <= t) { cout << 0 << '\n' ; return 0 ; } int l = 1 , r = n, mid; while (l <= r) { mid = (l + r) >> 1 ; if (judge (mid)) r = mid - 1 ; else l = mid + 1 ; } cout << r + 1 << '\n' ; return 0 ; }
三. 单调栈 两大用处: 1.NGE问题(Next Greater Element),也就是,对序列中每个元素,找到下一个比它大的元素。 2.两元素间所有元素均(不)大/小于这两者
(其实也可以用单调队列,不需要队首元素出队的单调队列)AC代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <bits/stdc++.h> using namespace std;int n;int ar[3000050 ];stack<int > st; int ans[3000050 ];int main () { scanf ("%d" , &n); for (int i = 1 ; i <= n; ++i) scanf ("%d" , &ar[i]); ans[n] = 0 ; st.push (n); for (int i = n - 1 ; i > 0 ; --i) { while (!st.empty () && ar[st.top ()] <= ar[i]) st.pop (); if (!st.empty ()) ans[i] = st.top (); else ans[i] = 0 ; st.push (i); } for (int i = 1 ; i <= n; ++i) printf ("%d " , ans[i]); putchar ('\n' ); return 0 ; }
(区间1~i内元素大小关系和单调栈内元素情况)AC代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <bits/stdc++.h> using namespace std;typedef long long ll;#define int ll int n;int ar[500050 ];stack<pair<int , int > > st; pair<int , int > pi; int ans, cnt;signed main () { scanf ("%lld" , &n); for (int i = 1 ; i <= n; ++i) scanf ("%lld" , &ar[i]); st.push ({ar[1 ], 1 }); for (int i = 2 ; i <= n; ++i) { int cnt = 1 ; while (!st.empty () && st.top ().first <= ar[i]) { pi = st.top (); st.pop (); ans += pi.second; if (pi.first == ar[i]) cnt = pi.second + 1 ; else cnt = 1 ; } if (!st.empty ()) ++ans; st.push ({ar[i], cnt}); } printf ("%lld\n" , ans); return 0 ; }
四. 单调栈优化dp 题意: 输入一个$n$,之后输入$n$个数字$ar[i]$。 让你选择一个点作为山峰。假设选择点$pos$作为山峰,其他的$n-1$点的高度将发生变化。假设修改后的高度记为$h[i]$,对于1~pos-1
中的一个点i
,必须满足$h[i]=min(h[i+1],ar[i])$,对于pos+1~n
中的一个点i
,必须满足$h[i] = min(h[i-1],ar[i])$。求$\sum_1^n h[i]$的最大值。输出取最大值时的h[i]
数组。分析: 首先,我们可以看一下easy版本 。区别在于n的大小。easy版本n最大1000。$O(n^2)$能过。我们考虑dp,定义dp1[i],dp2[i]
分别表示选择$i$作为山峰时$i$极其左侧的最大值。答案就是$max(dp1[i]+dp2[i]-ar[i])$。 我们考虑快速求dp1[i]
的方法,假设ar[j]
是$i$之前第一个满足ar[j]<=ar[i]
的元素,那么$dp1[i]=dp1[j]+(i-j)\times ar[i]$。而对于第一个小于等于的元素,显然可以利用单调栈快速找出。(dp2
求法同理)复杂度$O(n)$。 (单调栈用法1,利用单调栈找到区间内第一个比他大/小的元素)AC代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 #include <bits/stdc++.h> using namespace std;typedef long long ll;int n, pos;ll ar[500050 ]; ll dp1[500050 ]; ll dp2[500050 ]; ll ans[500050 ]; ll mx; stack<ll> st; void work () { int tmp = pos; ll mxx = ar[tmp]; ans[tmp] = ar[tmp]; while (tmp > 0 ) { --tmp; ans[tmp] = min (mxx, ar[tmp]); mxx = ans[tmp]; } tmp = pos; mxx = ar[tmp]; while (tmp < n) { ++tmp; ans[tmp] = min (mxx, ar[tmp]); mxx = ans[tmp]; } for (int i = 1 ; i <= n; ++i) printf ("%lld " , ans[i]); putchar ('\n' ); } int main () { scanf ("%d" , &n); for (int i = 1 ; i <= n; ++i) scanf ("%lld" , &ar[i]); st.push (0 ); for (int i = 1 ; i <= n; ++i) { while (!st.empty () && ar[st.top ()] >= ar[i]) st.pop (); dp1[i] = dp1[st.top ()] + (i - st.top ()) * ar[i]; st.push (i); } while (!st.empty ()) st.pop (); st.push (n + 1 ); for (int i = n; i > 0 ; --i) { while (!st.empty () && ar[st.top ()] >= ar[i]) st.pop (); dp2[i] = dp2[st.top ()] + (st.top () - i) * ar[i]; st.push (i); } for (int i = 1 ; i <= n; ++i) { if (dp1[i] + dp2[i] - ar[i] > mx) { mx = dp1[i] + dp2[i] - ar[i]; pos = i; } } work (); return 0 ; }
(295是正解,换成解绑之后的cin,cout用线段树暴力维护居然卡过了)。
题意: 输入一个n
,之后n
个数字ar[i]
。 一开始你在位置1
,你要到达位置n
,从位置i
到位置j
必须满足一下条件之一。 问你到达n
最少需要跳几步。分析: 单调栈的用法2,找两元素间所有元素均(不)大/小于这两者 (区间1~i内元素大小关系和单调栈内元素情况) 借助这个图理解和P1823 [COI2007] Patrik 音乐会的等待 这个题理解一下。AC代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <bits/stdc++.h> using namespace std;int n;stack<int > st1, st2; int dp[300050 ];int ar[300050 ];int main () { scanf ("%d" , &n); for (int i = 1 ; i <= n; ++i) scanf ("%d" , &ar[i]); memset (dp, 0x3f , sizeof (dp)); dp[1 ] = 0 ; for (int i = 1 ; i <= n; ++i) { dp[i] = min (dp[i], dp[i - 1 ] + 1 ); while (!st1. empty () && ar[st1. top ()] <= ar[i]) { int pos = st1. top (); st1. pop (); if (!st1. empty () && ar[i] > ar[pos]) dp[i] = min (dp[st1. top ()] + 1 , dp[i]); } st1. push (i); while (!st2. empty () && ar[st2. top ()] >= ar[i]) { int pos = st2. top (); st2. pop (); if (!st2. empty () && ar[i] < ar[pos]) dp[i] = min (dp[st2. top ()] + 1 , dp[i]); } st2. push (i); } printf ("%d\n" , dp[n]); return 0 ; }
题意: 输入一个n
,之后输入数组ar[n]
。你需要将区间[1,n]
分成k
个子区间,这k
个区间两两不交,且并集是全集。每个子区间的值是这个区间中最大的那个数字,你需要最小化这k
个区间的值的和。输出k
取1~n
时对应的答案。分析: 官方题解:
说人话:
AC代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <bits/stdc++.h> using namespace std;const int inf = 0x3f3f3f3f ;int n;int ar[8005 ];int dp[8005 ][8005 ];struct node { int val, mi, mi_dp; }st[8005 ]; int main () { scanf ("%d" , &n); for (int i = 1 ; i <= n; ++i) scanf ("%d" , &ar[i]); memset (dp, 0x3f , sizeof (dp)); dp[0 ][0 ] = 0 ; for (int j = 1 ; j <= n; ++j) { int top = 1 ; st[top] = {inf, inf, inf}; for (int i = 1 ; i <= n; ++i) { int mi = dp[i - 1 ][j - 1 ]; while (top >= 1 && st[top].val <= ar[i]) mi = min (mi, st[top--].mi); st[top + 1 ] = {ar[i], mi, min (mi + ar[i], st[top].mi_dp)}; ++top; dp[i][j] = st[top].mi_dp; } printf ("%d\n" , dp[n][j]); } return 0 ; }