知识点一:双端栈
将两个栈顶对顶使用,可以有出乎意料的效果
先上个题↓
功能强大编辑器
时间限制:1秒 内存限制:128M
题目描述
你要帮助小可创造一个超级数字编辑器!编辑器依旧运行在Linux下,因此你只能通过指令去操控他。指令有五种:In X
表示在光标左侧插入一个数字Del
表示删除光标左侧一个数字Left
表示光标向左移动一下Right
表示光标向右移动一下Ask k
表示光标之前的序列为a1,a2,a3…ak,输出max 1≤i≤k Si,其中Si=a1+a2+..+ai
输入描述
输入第一行包含一个整数Q,表示指令数量。
然后输入Q行,表示Q个指令。
输出描述
对于每个Ask k
均输出一行,表示询问结果。
输入样例
8
In 2
In -1
In 1
Ask 3
Left
Del
Right
Ask 2
输出样例
2
3
数据范围
50%的数据,Q不超过1000.
100%的数据,Q不超过1000000,且x不超过1000
解题思路
见到题的第一时间,可能会想到数组模拟,但这样每次操作都会牵动整个数组,极其麻烦且会超时。如果考虑双端栈,就会简单很多。
在这其中,有Ask操作比较特殊,要求出前缀和最大值,直接求会超时,因此我们可以在操作过程中维护最值;
对于in,right操作,都相当于在光标前新加一个数,到这个数的最大值即为上一个数的最值和这个数的前缀和的最大值(即不选此数或选此数)
AC代码
#include<bits/stdc++.h>
using namespace std;
stack<long long>s1,s2;
long long n,x,cnt[1000005],ma[1000005];
string k;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
ma[0]=-0x3f3f3f3f;
for(int i=1;i<=n;i++){//2 1
cin>>k;// ma2 2
if(k=="In"){// cnt2 2
cin>>x;
s1.push(x);
cnt[s1.size()]=cnt[s1.size()-1]+x;
ma[s1.size()]=max(ma[s1.size()-1],cnt[s1.size()]);
}
if(k=="Del"){
if(!s1.empty()){
//ma[s1.size()]=-0x3f3f3f3f,cnt[s1.size()]=-0x3f3f3f3f;
s1.pop();
}
}
if(k=="Left"){
if(!s1.empty()){
s2.push(s1.top());
//ma[s1.size()]=-0x3f3f3f3f,cnt[s1.size()]=-0x3f3f3f3f;
s1.pop();
}
}
if(k=="Right"){
if(!s2.empty()){
s1.push(s2.top());
cnt[s1.size()]=cnt[s1.size()-1]+s2.top();
ma[s1.size()]=max(ma[s1.size()-1],cnt[s1.size()]);
s2.pop();
}
}
if(k=="Ask"){
cin>>x;
cout<<ma[x]<<'\n';
}
}
}
知识点二:双端队列
单调队列
先上模板
滑动最小值
时间限制:1秒 内存限制:128M
题目描述
有一个序列,共有n个数字,请你求出以每个数字开始的长度为k的区间中的最小值。
输入描述
多组测试数据,输入0 0
表示结束。
第一行包含两个整数,n和k。
然后输入n个正整数。
输出描述
输出题面描述的答案,如果i>n-k,则无法查找到最小值,不进行输出。
输入样例
5 2
1 2 3 4 5
0 0
输出样例
1 2 3 4
数据范围
30%的数据:n<1000
100%的数据,n<1000000
解题思路
详见注释
AC代码
#include<bits/stdc++.h>
using namespace std;
deque<int>q;
int n,k,a[1000005];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
while(cin>>n>>k){
q.clear();
if(n==0&&k==0){
return 0;
}
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
if(i>k){//当遍历完第一个数后第k个再开始
cout<<q.front()<<' ';
if(q.size()>0&&q.front()==a[i-k]){//控制区间长度
q.pop_front();
}
}
while(!q.empty()&&q.back()>a[i]){//维护单调性
q.pop_back();
}
q.push_back(a[i]);
}
cout<<q.front()<<'\n';
}
}
另一例题
让小可睡一个好觉
时间限制:1秒 内存限制:128M
题目描述
小可最近太累了,昨天晚上为了写代码,小可睡觉的时间非常晚。恰好第二天是周六,小可打算好好睡一个懒觉,实现自己一个小小的愿望,睡觉睡到自然醒!为了实现这个愿望,小可肯定不可以被闹钟吵醒!
小可平时为了让自己早起学习,会定n个闹钟,如果长度为m的一段时间内,有K个及以上的闹铃响了,那么小可就会被吵醒,现在请你帮助一下小可,最少关掉多少个闹铃,小可不会被吵醒。
输入描述
输入第一行为n,m,k,含义如题所示。
第二行为每个铃响的时间ai。
输出描述
输出最少关闭几个闹钟,使小可能安稳的睡一个懒觉。
输入样例1
3 3 2
3 5 1
输出样例1
1
输入样例2
5 10 3
12 8 18 25 1
输出样例2
0
提示:
样例一中,可以关闭第3分钟响的闹钟;
样例二中,不需要关闭闹钟。
数据范围:
70%的数据下:1≤k≤n≤200,1≤m≤1000,ai≤3000
100%的数据下:1≤k≤n≤2∗10^5,1≤m≤10^6,ai≤10^6
解题思路
首先排序,使用双端队列,通过队尾-队头来控制时间区间长度,通过队列长度来判断区间内闹钟数量,超出时优先删除靠后的(贪心)
AC代码
#include<bits/stdc++.h>
using namespace std;
deque<int>q;
int n,k,ans,a[1000005];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>k>>ans;
int cnt=0;
q.clear();
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+1+n);
for(int i=1;i<=n;i++){
if(!q.empty()&&q.back()-q.front()>=k){
q.pop_front();
}
while(!q.empty()&&q.size()>=ans){
q.pop_back();
cnt++;
}
q.push_back(a[i]);
}
if(!q.empty()&&q.back()-q.front()>=k){
q.pop_front();
}
while(!q.empty()&&q.size()>=ans){
q.pop_back();
cnt++;
}
cout<<cnt;
}