单调队列
单调队列是什么?简单点来说,就是维护一个队列,是这个队列中的元素由大到小或有小到大排列。每当加入一个新的元素时,如果队列尾部的元素不满足单调性的话,就将队尾元素弹出,直到满足单调性再将新元素插入
单调队列操作
1,删头:如果队首的元素脱离了窗口,他就没用了,弹出。
2,去尾:如果新元素入队时,原队尾的存在打破了单调性,就弹出。
例题1:滑动窗口(洛谷P1886)
有一个长为 n 的序列a,以及一个大小为 k 的窗口。
现在这个从左边开始向右滑动,每次滑动一个单位,
求出每次滑动后窗口中的最大值和最小值
这里就体现了单调队列第一个也是最重要的的一个用处,求区间内最值。我们用一个单调递增队列,和一个单调递减队列分别来求区间内的最小值和最大值。
#include <bits/stdc++.h>
using namespace std;
const int N = 1000005;
int n , k , a[N];
deque<int> qz;//单调递增队列求最小值
deque<int> qj;//单调递减队列求最大值
int main()
{
scanf("%d%d" , &n , &k);
for(int i = 1 ; i <= n ; i++) scanf("%d" , &a[i]);
for(int i = 1 ; i <= n ; i++)
{
while(!qz.empty() && a[qz.back()] > a[i]) qz.pop_back();//去尾
qz.push_back(i);//记住是存下标!!!
if(i >= k)
{
while(!qz.empty() && i - qz.front() + 1 > k) qz.pop_front();//删头
printf("%d " , a[qz.front()]);
}
}
cout << endl;
for(int i = 1 ; i <= n ; i++)//同理
{
while(!qj.empty() && a[qj.back()] < a[i]) qj.pop_back();
qj.push_back(i);
if(i >= k)
{
while(!qj.empty() && i - qj.front() + 1 > k) qj.pop_front();
printf("%d " , a[qj.front()]);
}
}
return 0;
}
例题2:良好的感觉(洛谷P2422)
kkk 做了一个人体感觉分析器。每一天,人都有一个感受值 A i
A i越大,表示人感觉越舒适。在一段时间 [i,j]内,人的舒适程度定义为:[i,j] 中最不舒服的那一天的感受值 * [i,j]中每一天感受值的和。
现在给出 kkk 在连续 N 天中的感受值,
请问,在哪一段时间,kkk 感觉最舒适?(1<=A<=100000)
因为A为正数所以A尽量多,我们以每个节点为中心,向两边扩展,直到两边碰到比他小的就停止
预处理出前缀和,用每个点乘以这段区间的长度 取最大值即可
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
long long n , a[N] , sum[N];
int frt[N] , nxt[N];//frt[i]表示i前面第一个比他小的数的下标,nxt[i]表示i后面第一个比他小的数的下标
deque<int> q1;
deque<int> q2;
int main()
{
scanf("%lld" , &n);
for(int i = 1 ; i <= n ; i++)
{
scanf("%lld" , &a[i]);
sum[i] = sum[i - 1] + a[i];//前缀和
}
for(int i = 1 ; i <= n ; i++)
{
while(!q1.empty() && a[q1.back()] > a[i])
{
nxt[q1.back()] = i - 1;//a[i]比a[q1.back()]小,所以nxt[q1.back()] = i - 1;
q1.pop_back();
}
q1.push_back(i);
}
for(int i = 1 ; i <= n ; i++)
if(nxt[i] == 0)
nxt[i] = n;//如果a[i]后面没有比他小的,就直接到最后
for(int i = n ; i >= 1 ; i--)//求前面需要倒着输入
{
while(!q2.empty() && a[q2.back()] > a[i])
{
frt[q2.back()] = i + 1;//a[i]比a[q2.back()]小,所以frt[q2.back()] = i + 1;
q2.pop_back();
}
q2.push_back(i);
}
for(int i = 1 ; i <= n ; i++)
if(frt[i] == 0)前面没有比他小的,就直接到开头
long long maxn = 0;
for(int i = 1 ; i <= n ; i++) maxn = max(maxn , (sum[nxt[i]] - sum[frt[i] - 1]) * a[i]);
printf("%lld" , maxn);
return 0;
}
习题:选数1(accoders 9750)
有N个数(N<=100000),在连续M(M<=N)个数里至少要有一个数被选择。
求选出来数的最小总和
思路都在代码中,可能略微简洁,请读者自行理解,如有困惑可以私信问我。
/*思路
单调队列优化dp
Dp[i]表示前i个数都满足条件的最小值
Dp[i] = dp[j] + a[i] (i - m <= j <= i - 1)
用单调队列找dp[i-m] 到 dp[i-1] 的最小值 */
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int n , m , a[N] , dp[N];
deque<int> q;
int main()
{
scanf("%d%d" , &n , &m);
for(int i = 1 ; i <= n ; i++) scanf("%d" , &a[i]);
q.push_back(0);
for(int i = 1 ; i <= n ; i++)
{
while(!q.empty() && i - q.front() > m) q.pop_front();
dp[i] = dp[q.front()] + a[i];
while(!q.empty() && dp[q.back()] > dp[i]) q.pop_back();
q.push_back(i);
}
int ans = 2147483647;
for(int i = n - m + 1 ; i <= n ; i++) ans = min(ans , dp[i]);
printf("%d" , ans);
return 0;
}
温馨提示:本人经常在洛谷,CF,accoders上做题,大家可以注册一下,一起努力吧。