单调队列以及单调栈

”如果一个选手比你小还比你强,那你就该退役了“————单调队列原理
单调队列多用于滑动窗口问题,即区间最优值的问题,与优先队列和线段树\(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?

posted @ 2024-04-16 18:03  WE-R  阅读(6)  评论(0)    收藏  举报