单调队列以及单调栈
”如果一个选手比你小还比你强,那你就该退役了“————单调队列原理
单调队列多用于滑动窗口问题,即区间最优值的问题,与优先队列和线段树\(O(nlogn)\)的时间复杂度相比,有着高贵的\(O(n)\)时间复杂度。
区间最大(小)值
区间最值是单调队列的一个经典应用。例如给出一段序列
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|
| 3 | 2 | 1 | 4 | 6 | 5 | 7 |
初始队列中应该是\(<3,2,1>\)
对于这样的一段序列,加入我们要求长度为3的区间的最大值,我们先从第4个开始看起
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|
| [ 3 | 2 | 1 | 4 ] | 6 | 5 | 7 |
当第4个数加入判断时,首先注意到3的下标是1,已经不在窗口中了,我们应当直接排除掉,而除此之外的前面的2个数都比4要小,这也就意味着,在后续几个窗口的答案中,如果答案不是4,那结果也肯定不会是\(1,2\),这启示我们可以直接忽视这两个数字。
于是队列中应该是
\(<4>\)
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|
| 3 | 2 | [1 | 4 | 6] | 5 | 7 |
第5个数字进入同上。
\(<6>\)
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|
| 3 | 2 | 1 | [4 | 6 | 5] | 7 |
而当第六个数字5进入窗口时,事情变得有趣了一点,5比6要小,这提示5并不能作为6的完美上位替代,我们无需将6弹出。
\(<6,5>\)
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|
| 3 | 2 | 1 | 4 | [6 | 5 | 7] |
第7个数字进来就变成了\(<7>\)
而每次更新后队列的第一个答案就是我们所求的结果,而这些队列中的数具有一定的单调性,于是自然而然地就有了单调队列的名号。
附上代码
deque版本
点击查看代码
deque<int> Q;
int a[N];
for(int i=0;i<n;i++) {
while( (!Q.empty()) && Q.front() < i-k+1 ) {
Q.pop_front();
}
while( (!Q.empty()) && a[i]>=a[Q.back()]) {
Q.pop_back();
}
Q.push_back(i);
}
数组版本
点击查看代码
int a[N];
int Q[N];
int le=0,ri=-1;
for(int i=0;i<n;i++) {
while( le<=ri && Q[le]<i-k+1) {
++le;
}
while( le<=ri && a[i]>=a[Q[ri]]) {
--ri;
}
Q[++ri]=i;
}
例题
CF E. Rudolf and k Bridges
洛谷 Rudolf and k Bridges
有一条\(n \times m\)的河。第\(i\)行第\(j\)列的深度为\(a_{i,j}\)。保证\(a_{i,1}=a_{i,m}=0\)
如果在第\(i\)行第\(j\)列安置桥墩,所需代价为\(a_{i,j}+1\)
你需要选择连续的\(k\)行,每行都要架起若干个桥墩,并满足以下条件:
1. 每行的第\(1\)列必须架桥墩;
2. 每行的第\(m\)列必须架桥墩;
3. 每行的相邻两个桥墩的距离不超过\(d\)。其中\((i,j_1)\)和\((i,j_2)\)之间的距离为\(|j_1 - j_2 |-1\)
求最小代价和。
一眼DP,用单调队列维护符合条件距离中的代价最小的即可。其中\(dp_j\)表示某一行第j列在这里建桥墩的最小代价。
点击查看代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<map>
#include<cstdlib>
#include<set>
#include<vector>
#include<cmath>
#include<iostream>
using namespace std;
#define endl '\n'
const int N=2e5+10;
// const int mod=1e9+7;
#define ll long long
#define ull unsigned long long
#define int ll
const int mod=0x3fffffffffffffff;
int a[N];
struct node {
int i,x;
};
// vector<node> s;
// node s[N];
// int dp[N];
// vector<int> dp;
int x[120];
void solve() {
int n,m,k,d;
cin>>n>>m>>k>>d;
// scanf("%d%d%d%d",&n,&m,&k,&d);
// printf("n is %d\n",n);
int i,j;
for(i=1;i<=n;i++) {
x[i]=0;
}
a[0]=0;
for(i=1;i<=n;i++) {
// dp.clear();
// dp.resize(n+1);
vector<int> dp(m+10);
dp[0]=0;
// printf("now is %d row\n",i);
for(j=1;j<=m;j++) {
cin>>a[j];
// scanf("%d",a+j);
dp[j]=0;
}
int le=1,ri=0;
// s.clear();
// s.resize(n+1);
vector<node> s(m+10);
s[1]=(node) {0,0};
for(j=1;j<=m;j++) {
while(le<ri&&s[le].i<j-d-1) {
++le;
}
while(le<ri&&s[ri].x>=s[le].x+a[j]+1) {
ri--;
}
// printf("s[%d]->%d\n",le,s[le].x);
s[++ri]=(node) {j,s[le].x+a[j]+1};
dp[j]=s[ri].x;
// printf("%d ",dp[j]);
}
x[i]=dp[m];
// printf("x[%d]=%d %d\n",i,x[i],dp[m]);
}
int tmp=0,ans=mod;
for(i=1;i<=k;i++) {
tmp+=x[i];
}
ans=min(ans,tmp);
for(i=k+1;i<=n;i++) {
tmp-=x[i-k];
tmp+=x[i];
ans=min(ans,tmp);
}
cout<<ans<<endl;
return;
}
// int main() {
signed main() {
ios_base::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin>>t;
while(t--)
solve();
return 0;
}
CF E. Explosions?
洛谷 Explosions?

浙公网安备 33010602011771号