单调栈
单调栈,其实就是维护栈里面的序列是单调的,当压入单调栈的元素将打破单调性的时,弹出里面的元素,直到将这个元素压入栈时能够满足单调性。
vector<int>v;
stack<int>s;
for (int i = 1; i <= n; ++ i){
while (!s.empty() && v[s.top()] >= v[i]){ // 等于不等于看题目
s.pop();
}
s.push(i);
}
例题 洛谷P5788
这道题要找的是第 i i i个元素后从第 i + 1 i+1 i+1个元素到第 n n n个元素中第一个大于 a i a_i ai的元素的下标,将序列反转一下就成了找到到这个元素之前的元素中最后一个大于它的元素,那么利用单调栈维护单调性的特点,从后往前,维护一个递减的序列即可。
代码
#include <bits/stdc++.h>
using namespace std;
const int LEN = 3e6 + 115;
int arr[LEN] = {0, }, ans[LEN] = {0, }, n;
stack<int>s;
int main(){
#ifndef ONLINE_JUDGE
freopen("in.in", "r", stdin);
#endif
std::ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n;
for (int i = 1; i <= n; ++ i){
cin >> arr[i];
}
for (int i = n; i > 0; -- i){
while (!s.empty() && arr[s.top()] <= arr[i]){
s.pop();
}
if (s.empty()){
ans[i] = 0;
}else{
ans[i] = s.top();
}
s.push(i);
}
for (int i = 1; i <= n; ++ i){
cout << ans[i] << ' ';
}
return 0;
}
单调队列
单调队列和单调栈一样,队列里面是单调的序列(单调指的是自定义的一种状态,比如递增,或者非递减)。和普通队列不同的是,单调队列两端都可以弹出,不过入队操作仍然是在一段进行。
例题 洛谷P1714
题目分析
题目需要找的,是数列中最多 m m m个数的和的最大值,如果0个数的话,答案就为0。而快速查找到连续几个数的和的最佳方式,就是直接查找其前缀和,能够快速查找到这几个数的和。因此,只需要维护 m m m个前缀和中的最小值,与当前前缀和相减,即可得到答案。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int LEN = 5e5 + 115;
LL arr[LEN] = {0, }, x, n, m, ans = 0;
deque<LL>deq; // 这里单调队列用的是stl的deque
int main(){
#ifndef ONLINE_JUDGE
freopen("in.in", "r", stdin);
#endif
std::ios::sync_with_stdio(false);
cin >> n >> m;
for (int i = 1; i <= n; ++ i){
cin >> x;
arr[i] = x + arr[i - 1];
}
deq.push_back(0); // 没有吃蛋糕自然为0
for (int i = 1; i <= n; ++ i){
while (deq.front() + m < i){ // 确保最小值是在 i-m 的范围内
deq.pop_front();
}
ans = max(ans, arr[i] - arr[deq.front()]); // 获取最佳答案
while (!deq.empty() && arr[deq.back()] >= arr[i]){ // 入队的时候需要维护一下其单调性
deq.pop_back();
}
deq.push_back(i);
}
cout << ans << endl;
return 0;
}
例题 洛谷 P1725
这是一道线性dp,每一步只能从 i i i到 [ i + l , i + r ] [i+l, i+r] [i+l,i+r]的格子,那么自然这个位置的代价一定是从能走过来的格子 [ i − r , i + l ] [i-r, i+l] [i−r,i+l]内转移过来,动规方程为 f ( i ) = m a x { f ( j ) ∣ i − r ≤ j ≤ i − l } + c o s t ( i ) f(i) = max\left\{ f(j) \, | \, i - r \le j \le i - l \right\} + cost(i) f(i)=max{f(j)∣i−r≤j≤i−l}+cost(i),于是,可以写出这样的代码。
#include <bits/stdc++.h>
#define endn "\n"
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int LEN = 1e4 + 10;
LL dp[LEN] = {0, }, n, l, r, x, arr[LEN] = {0, }, ans = -0x7FFFFFFFF;
void solve(){
cin >> n >> l >> r;
for (int i = 0; i <= n; ++ i){
cin >> arr[i];
}
for (int i = l; i <= n; ++ i){
for (int j = i - l; j >= 0 && j >= i - r; -- j){
dp[i] = max(dp[i], dp[j] + arr[i]);
}
if (i + r >= n){
ans = max(ans, dp[i]);
}
}
cout << ans << endn;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in.in", "r", stdin);
#endif
std::ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
solve();
return 0;
}
然后你就发现TLE了
但是事实上只要观察上面代码中那个枚举 d p [ j ] + a r r [ i ] dp[j] + arr[i] dp[j]+arr[i]的过程,如果能直接找到最大的 d p [ j ] dp[j] dp[j]不是更好吗。再仔细观察一下,这个枚举就是找区间中的最大值,那么直接用单调队列枚举不就可以了嘛。
利用单调队列优化dp
#include <bits/stdc++.h>
#define endn "\n"
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int LEN = 1e4 + 10;
LL dp[LEN] = {0, }, n, l, r, x, arr[LEN] = {0, }, ans = -0x7FFFFFFFF;
deque<LL>deq;
void solve(){
cin >> n >> l >> r;
for (int i = 0; i <= n; ++ i){
cin >> arr[i];
}
for (int i = l; i <= n; ++ i){
while (!deq.empty() && dp[deq.back()] < dp[i - l]){
deq.pop_front();
}
deq.push_back(i - l);
while (deq.front() < i - r){
deq.pop_front();
}
dp[i] = dp[deq.front()] + arr[i];
ans = max(ans, dp[i]);
}
cout << ans << endn;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in.in", "r", stdin);
#endif
std::ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
solve();
return 0;
}