知识点
单调栈
就是有序的栈,分为递增栈和递减栈。
如何维护栈的有序性呢?
假设有一个序列 {5 3 8 9 1},要构建递增栈:
5入栈
单调栈:5
3<5,因此先将5出栈,3入栈
单调栈:3
8>3,8直接入栈
单调栈:3 8
9>8,9直接入栈
单调栈:3 8 9
1<9,因此先将8,9出栈,1入栈
单调栈:1
我们可以发现,构建递增栈:当要入栈元素可以顺利入栈时,此时的栈顶为要入栈元素左侧第一个小于它的数;构建递增栈,当要入栈元素可以顺利入栈时,此时的栈顶为要入栈元素左侧第一个小于它的数
实现代码:
#include<iostream>
#include<stack>
#include<cstdio>
using namespace std;
stack<int> st;
int main(){
int n,x;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&x);
while(!st.empty() && st.top()>=x){
st.pop();
}
st.push(x);
}
return 0;
}
单调队列
与单调栈相似,为了维护队列的有序性,我们采用双端队列。
单调队列很多时候可以代替单调栈
因为双端队列可以从两头进行入队出队,所以它可以控制队内元素个数,当发现数量超出限制,则从队头出队一些元素。
实现代码:
#include<iostream>
#include<deque>
#include<cstdio>
using namespace std;
int a[1000005],n,k;
deque<int> dq;
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(!dq.empty() && a[dq.back()]>=a[i]){
dq.pop_back();
}
dq.push_back(i);
if(dq.front()<i-k+1){
dq.pop_front();
}
}
return 0;
}
例题
题目:理想的正方形
时间限制:1秒 内存限制:128M
题目描述
有一个n×m的整数组成的矩阵,现请你从中找出一个k×k的正方形区域,使得该区域所有数中的最大值和最小值的差最小。
输入样例
5 4 2
1 2 5 6
0 17 16 0
16 17 2 1
2 10 2 1
1 2 2 2
输出样例
1
数据描述
2 ≤ n , m ≤ 1000 , k ≤ n , k ≤ m , k ≤ 100 2≤n,m≤1000,k≤n,k≤m,k≤100 2≤n,m≤1000,k≤n,k≤m,k≤100
理想的正方形解答
思路
这是一个二维单调队列问题,先求出每一行到j的最小值和最大值,再求一边每一列的最大值和最小值,最后作差求最小值。(第二遍要求列,所以外层循环按列遍历内层按行遍历)
代码
#include<iostream>
#include<deque>
#include<cstdio>
using namespace std;
int a[1005][1005],n,m,k,r_minn[1005][1005],r_maxx[1005][1005];
deque<int> Max,Min;
int main(){
scanf("%d %d %d",&n,&m,&k);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
while(!Min.empty() && a[i][Min.back()]>=a[i][j]){
Min.pop_back();
}
Min.push_back(j);
if(Min.front()<j-k+1){
Min.pop_front();
}
while(!Max.empty() && a[i][Max.back()]<=a[i][j]){
Max.pop_back();
}
Max.push_back(j);
if(Max.front()<j-k+1){
Max.pop_front();
}
if(j>=k){
r_minn[i][j]=a[i][Min.front()];
r_maxx[i][j]=a[i][Max.front()];
}
}
Max.clear();
Min.clear();
}
int ans=1e9;
for(int j=k;j<=m;j++){
for(int i=1;i<=n;i++){
while(!Min.empty() && r_minn[Min.back()][j]>=r_minn[i][j]){
Min.pop_back();
}
Min.push_back(i);
if(Min.front()<i-k+1){
Min.pop_front();
}
while(!Max.empty() && r_maxx[Max.back()][j]<=r_maxx[i][j]){
Max.pop_back();
}
Max.push_back(i);
if(Max.front()<i-k+1){
Max.pop_front();
}
if(i>=k){
ans=min(r_maxx[Max.front()][j]-r_minn[Min.front()][j],ans);
}
}
Max.clear();
Min.clear();
}
printf("%d",ans);
return 0;
}
题目:发射站
时间限制:1秒 内存限制:128M
题目描述
某地有N个能量发射站排成一行,每个发射站i都有不相同的高度 H i H_i Hi ,并能向两边(两端的发射站只能向一边)同时发射能量值为 V i V_i Vi
的能量,发出的能量只被两边最近的且比它高的发射站(位置比他高的)接收。显然,每个发射站发来的能量有可能被0或1或2个其他发射站所接受。
请计算出接收最多能量的发射站接收的能量是多少。
输入样例
3
4 2
3 5
6 10
输出样例
7
数据描述
对于40%的数据, 1 ≤ N ≤ 5000 , 1 ≤ H i ≤ 1 0 5 , 1 ≤ V i ≤ 1 0 4 1≤N≤5000,1≤H_i ≤10^5 ,1≤V_i ≤10^4 1≤N≤5000,1≤Hi≤105,1≤Vi≤104
对于70%的数据, 1 ≤ N ≤ 1 0 5 , 1 ≤ H i ≤ 2 × 1 0 9 , 1 ≤ V i ≤ 1 0 4 1≤N≤10^5,1≤H_i ≤2×10^9 ,1≤V_i ≤10^4 1≤N≤105,1≤Hi≤2×109,1≤Vi≤104
对于100%的数据, 1 ≤ N ≤ 1 0 6 , 1 ≤ H i ≤ 2 × 1 0 9 , 1 ≤ V i ≤ 10 4 1≤N≤10^6,1≤H_i≤2×10^9 ,1≤V_i ≤10^4 1≤N≤106,1≤Hi≤2×109,1≤Vi≤104
发射站解答
思路
这就是在求每个数左边第一个大于他的数和右边第一个大于它的数,然后向求出来的塔增加接收到的能量,最后求最大值。(每轮要入栈的元素就是所有从栈中弹出元素右侧第一个大于他们的数)
代码
#include<iostream>
#include<stack>
#include<cstdio>
using namespace std;
stack<int> st;
int v[1000005],h[1000005],ans[1000005],maxx=0;
int main(){
int n,x;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d %d",&h[i],&v[i]);
while(!st.empty() && h[st.top()]<h[i]){
ans[i]+=v[st.top()];
st.pop();
}
if(!st.empty()){
ans[st.top()]+=v[i];
}
st.push(i);
}
for(int i=1;i<=n;i++){
maxx=max(ans[i],maxx);
}
printf("%d",maxx);
return 0;
}