区队A 单调栈&单调队列

Pay attention

Don't forget to define the size of vector

单调栈专题

两种模版

vector版本

#include<bits/stdc++.h>
using namespace std;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	int n;
	cin>>n;
	vector<int> a(n+1);
	for(int i = 1;i<=n;i++){
		cin>>a[i];
	}
	vector<int> stack;
	stack.reserve(n);
	vector<int> ans(n + 1);
	for(int i = n;i>0;--i){
		for(;!stack.empty() && a[stack.back()] <= a[i];stack.pop_back());
		ans[i] = !stack.empty() ? stack.back() : 0;
		stack.push_back(i);
	}
	for(int i = 1;i<=n;i++){
		cout<<ans[i]<<' ';
	}
	return 0;
}

数组模拟版

#include<bits/stdc++.h>
using namespace std;
const int N = 3e6+10;
int a[N],ans[N],stk[N],top,n;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n;
	for(int i = 1;i<=n;i++) cin>>a[i];
	top= 0;
	for(int i = 1;i<=n;i++){
		while(top && a[stk[top]] < a[i]){
			ans[stk[top]] = i;
			top--;
		}
		stk[++top] = i;
	}
	while(top) ans[stk[top--]] = 0;
	for(int i = 1;i<=n;i++){
		cout<<ans[i]<<' ';
	}
	return 0;
}

P5788 【模板】单调栈

思路:板子题,每次都去用栈维护单调性

时间复杂度:由于每个元素最多被出入栈两次,所以复杂度为\(O(2*n)\),通常算作\(O(n)\)

代码:根据上面的模版直接复制即可

B3666 求数列所有后缀最大值的位置

重要知识点铺垫:异或符号为\(^\),然后\(a\) $ ^$ \(a\) = 0

思路:可以看出本题可以使用单调栈进行维护,那么在单调栈更新时去完成异或的操作

时间复杂度:因为这里的维护操作位于单调栈维护中,所以为\(O(n)\)

注意:stk数组记得提前确定大小,然后\(2^{64}-1\)要使用无符号的long long,也就是unsigned long long

代码:

#include<bits/stdc++.h>
using namespace std;
#define int unsigned long long
const int N  = 1e6+10;
int n;


signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n;
    vector<int> stk(n+1);
    vector<int> a(n+1);
    for(int i =1;i<=n;i++){
        cin>>a[i];
    }
    stk.reserve(n);
    int ans = 0;
    for(int i = 1;i<=n;i++){
        while(!stk.empty() && a[stk.back()] <= a[i]){
            ans ^= stk.back();
            stk.pop_back();
        }
        ans ^= i;
        stk.push_back(i);
        cout<<ans<<'\n';
    }
    return 0;
}

P8094 [USACO22JAN] Cow Frisbee S

思路:因为需要让两头奶牛中间没有比他们两个最矮的高的所以就是单调栈去完成,每一个去找到它可以配对的,并统计总和。

时间复杂度:因为只需要每个奶牛的一个枚举,所以为\(O(n)\),而不是暴力两两配对的\(O(n^2)\)

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e5+10;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	int n;
	cin>>n;
	vector<int> a(n + 1);
	for(int i= 1;i<=n;i++) cin>>a[i];
	vector<int> stk;
	stk.reserve(n);
	int ans = 0;
	for(int i = 1;i<=n;i++){
		while(!stk.empty() && a[stk.back()] < a[i]){
			ans += i - stk.back() + 1;
			stk.pop_back();
		}
		if(!stk.empty()){
			ans += i - stk.back() + 1; 
		}
		stk.push_back(i);
	}	
	cout<<ans;
	return 0;
}

P4147 玉蟾宫

思路:

  1. 首先处理数据,可以先将矩阵转换为二维数组,在一个位置为'F'时标记为1,否则为0
  2. 逐行遍历数组,对每一列去维护一个高度数组,\(sum[j]\):以当前行为底,第j列连续的'F'的高度,遇到'R'就重置为0
  3. 对每行的sum数组进行单调栈计算这个图里面可以构成的最大矩阵面积
  4. 每次的单调栈中去进行取答案的操作,并最后输出这个答案乘3

实现细节:

  1. 弹出的条件是栈不为空且栈顶高度>当前高度,然后进行弹出栈顶并计算面积,并累计弹出元素的宽度,更新答案
  2. 完成上一步后将当前的高度和累计宽度+1压入栈中
  3. 最后处理栈中剩余的元素,和上面逻辑一样

时间复杂度:为\(O(n*m)\)

代码:

#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
const int N = 1010;
int n,m;
int sum[N];
int M[N][N];


signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>m;
	for(int i= 0;i<n;i++){
		for(int j = 0;j<m;j++){
			char c;
			cin>>c;
			if(c == 'R') M[i][j] = 0;
			else M[i][j] = 1;
		}
	}
	vector<pii> stack;
	stack.reserve(m);
	int maxx = 0;
	for(int i = 0;i<n;i++){
		for(int j = 0;j<m;j++){
			sum[j] = M[i][j] ? sum[j] + 1 : 0;
			int cnt = 0;
			while(!stack.empty() && stack.back().first > sum[j]){
				auto p = stack.back();
				stack.pop_back();
				cnt += p.second;
				maxx = max(maxx,p.first * cnt);
			}
			stack.emplace_back(sum[j],cnt+1);
		}int cnt = 0;
		while(!stack.empty()){
			auto p = stack.back();
			stack.pop_back();
			cnt += p.second;
			maxx = max(maxx,p.first * cnt);
		}
	}
	cout<<maxx * 3;
	return 0;
}

P3400 仓鼠窝

思路:

  1. 注意到n,m \(\le\) 3000,所以\(O(nm)\)算法可以解决
  2. 所以统计每一个点作为右下角时可以得到多少矩阵并求和
  3. 每次循环需要清空单调栈然后统计本列最低的0
  4. 单调栈去排除比当前点靠左并高的点
  5. 先继承左列的答案在加上当前答案
  6. 公式为\(sum[top] = sum[top - 1] + height * width\)

Code:

#include<bits/stdc++.h>
using namespace std;
#define int unsigned long long
const int N = 3010;
int n,m;
int a[N][N];
int f[N];
int ans =0;

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n>>m;
    for(int i = 1;i<=n;i++){
        for(int j = 1;j<=m;j++){
            cin>>a[i][j];
        }
    }
    for(int i = 1;i<=n;i++){
        vector<int> stack;
        stack.reserve(m);
        vector<int> sum(m+1,0);
        for(int j = 1;j<=m;j++){
            if(!a[i][j]){
                f[j] = i;
            }
            while(!stack.empty() && f[stack.back()] < f[j]){
                stack.pop_back();
            }
            stack.push_back(j);
            int top = stack.size();
            int height = i - f[stack[top - 1]];
            int width = stack[top - 1] - (top > 1 ? stack[top- 2] : 0);
            sum[top] = sum[top - 1] + height * width;
            ans += sum[top];
        }
    }cout<<ans;

    return 0;
}

[COCI 2018/2019 #1] Strah

pass,不会

P1886 【模板】单调队列 / 滑动窗口

思路:

  1. 单调队列最重要的是单调性与使用队列进行维护
  2. 所以在加入新元素时从队尾踢掉所有不大于/小于它的元素,并加入队列
  3. 从队头移除已经离开的元素

Code:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+10;
int a[N],min_ans[N],max_ans[N];
int n,k;
deque<int> min_q;
deque<int> max_q;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>k;
	for(int i = 1;i<=n;i++){
		cin>>a[i];
	}	
	for(int i = 1;i<=n;i++){
		while(min_q.size() && a[min_q.back()] > a[i]){
			min_q.pop_back();
		}min_q.push_back(i);
		if(min_q.size() && min_q.front() <= i - k){
			min_q.pop_front();
		}
		if(i >= k){
			min_ans[i - k + 1] = a[min_q.front()];
		}
	}
	for(int i = 1;i<=n;i++){
		while(max_q.size() && a[max_q.back()] < a[i]){
			max_q.pop_back();
		}max_q.push_back(i);
		if(max_q.size() && max_q.front() <= i - k){
			max_q.pop_front();
		}
		if(i >= k){
			max_ans[i - k + 1] = a[max_q.front()];
		}
	}
	for(int i= 1;i<=n - k + 1;i++){
		cout<<min_ans[i]<<' ';
	}
	cout<<'\n';
	for(int i = 1;i<=n - k + 1;i++){
		cout<<max_ans[i] << ' ';
	}
	return 0;
}

P2629 好消息,坏消息

思路:
先完成断环为链,实现前缀和,用单调队列直接维护

Code:

#include<bits/stdc++.h>
using namespace std;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	int n;
	cin>>n;
	vector<int> a(2 * n+10);
	for(int i =1;i<=n;i++){
		cin>>a[i];
		a[i+n] = a[i];
	}
	int m = n * 2 - 1;
	for(int i = 2;i<=m;i++){
		a[i] += a[i - 1];
	}
	int ans= 0;
	vector<int> queue(2*n+10);
	int head = 0,tail = -1;
	for(int i = 1;i<=m;i++){
		if(head <= tail && queue[head] == i - n){
			++head;
		} 
		for(;head <= tail && a[queue[tail]] > a[i];--tail){}
		queue[++tail] = i;
		if(i >= n && a[queue[head]] >= a[i - n]){
			++ans;
		} 
	}
	cout<<ans;
	return 0;
}
posted @ 2026-09-08 13:13  Zhenggeek  阅读(5)  评论(0)    收藏  举报