数据结构(1) 单调栈、单调队列、ST表

介绍一些简单的数据结构。

单调栈

单调栈其实就是维护一个栈使得栈内元素从底到顶保持单调递增或其他。在加入元素时将栈顶所有小于插入元素的弹出栈即可维护。时间复杂度为线性。

主要应用场景有寻找上一个或下一个特殊的数,比如更大的数或更小的。

Code.
#include<bits/stdc++.h>
#define endl "\n"
#define pb push_back
#define mkp make_pair
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
//value 
const int inf=2147483647;
const int mod=1e9+7;
int a[3000005],ans[3000005];
stack<pair<int,int> >q;


//function 
void solve(){
	
	
	
	return;
}


 
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0); 
	
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++){
		while(!q.empty() && q.top().second<a[i]){
			ans[q.top().first]=i;
			q.pop();
		}
		q.push(mkp(i,a[i]));
	}
	while(!q.empty()) {
		ans[q.top().first]=0;
		q.pop();
	}
	
	for(int i=1;i<=n;i++)cout<<ans[i]<<' ';
	
	
	
	return 0;
}


我们还可以用单调栈离线解决区间极值问题,但整体上复杂度劣于 ST 表解决,故只引用链接

单调队列

是的孩子们我早被单调队列了

单调队列又称滑动窗口,单调队列通常处理的问题是固定长度区间中的最大值或最小值。

假设我们需要找固定长度区间中的最大值,我们可以维护一个双端队列,像单调栈一样操作,使得里面的元素单调递减且元素均属于要求的区间最大值,每轮判断是队列头是该定区间最大值。

让我们来思考这个东西为什么是对的。首先如果我在这轮新加入的元素是大于前面的元素的,由于前面的小的元素会更早被弹出且同时在区间中还有新插入的更大的元素,故它不可能再成为答案。而我们的维护方法又保证了再单调队列中最大的元素在队头,故正确性得证。

Code.
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int a[N];
void solve(){
    int n,k;
    cin>>n>>k;
    for(int i=0;i<n;i++)cin>>a[i];
    deque<int> dq; // 存放下标
    // 先求最小值
    
    for(int i=0;i<n;i++){
        // 窗口为以 i 为右端点,大小为 k 的区间,即窗口为 [i + 1 - k, i]
        // 如果队列非空,并且队头下标非法,即对头元素在窗口外
        while(!dq.empty()&&dq.front()<i+1-k)dq.pop_front(); 
        // 弹出队头
        // 如果队列非空,且当前元素比队尾更小更优越
        while(!dq.empty()&&a[i]<=a[dq.back()])dq.pop_back(); 
        // 弹出队尾
        dq.push_back(i); // 当前元素下标入队尾
        // 此时队头为最小值
        // 前几次时还没有形成大小为 k 的窗口,形成后队头即为最小值
        if(i>=k-1)cout<<a[dq.front()]<<' ';
    }
    
    cout<<endl;
    dq=deque<int>(); // 清空 dq
    // 再求最大值
    
    for(int i=0;i<n;i++){
        // 窗口为以 i 为右端点,大小为 k 的区间,即窗口为 [i + 1 - k, i]
        // 如果队列非空,并且队头下标非法,即对头元素在窗口外
        while(!dq.empty()&&dq.front()<i+1-k)dq.pop_front(); 
        // 弹出队头
        // 如果队列非空,且当前元素比队尾更大更优越
        while(!dq.empty()&&a[i]>=a[dq.back()])dq.pop_back(); 
        // 弹出队尾
        dq.push_back(i); // 当前元素下标入队尾
        // 此时队头为最大值
        // 前几次时还没有形成大小为 k 的窗口,形成后队头即为最大值
        if(i>=k-1)cout<<a[dq.front()]<<' ';
    }
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0); 
    cout.tie(0);
    int T=1;
    // cin >> T;
    while(T--)solve();
    
    return 0;
}

ST 表

ST 表是一个维护区间极值的数据结构。(其实更规范的叫法是可重复贡献问题)

ST 表基于倍增思想,约为 \(O(n \log n)\) 的预处理和 \(O(1)\) 的查询。

设我们要求出每个 \(l\)\(r\) 中的最大值,主要思路是将每个位置之后的 \(2^i\) 的区间中的最大值求出,并递推出 \(2^{i+1}\) 的区间最大值。基于这个预处理方式,ST 表只支持维护静态区间最大值。

而我们在查询最大值的时候,由于最大值可重复,故我们求出两个区间(可能包括重叠部分)的最大值同时合并两个最大值得出答案。

Code.
#include<bits/stdc++.h>
#define endl "\n"
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
//value 
int n,m,x,y;
int a[100010],st[100010][25];
int lg[100010];

//function 

 
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0); 
	
	cin>>n>>m;
//	lg[1]=0;
//	for (int i=2;i<=n;i++) lg[i]=lg[i>>1]+1;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++)st[i][0]=a[i];
	for(int j=1;j<=21;j++){
		for(int i=1;i+(1<<j)-1<=n;i++){
			st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
		}
	}
	for(int i=1;i<=m;i++){
		cin>>x>>y;
		int l=log2(y-x+1);
		cout<<max(st[x][l],st[y-(1<<l)+1][l])<<endl;
	}
	
	return 0;
}

posted @ 2025-07-15 15:47  -Delete  阅读(7)  评论(0)    收藏  举报