队列,堆,栈总结

 _  ___             _______
| |/ (_)           |___  / |
| ' / _ _ __   __ _   / /| |__   __ _ _ __   __ _
|  < | | '_ \ / _` | / / | '_ \ / _` | '_ \ / _` |
| . \| | | | | (_| |/ /__| | | | (_| | | | | (_| |
|_|\_\_|_| |_|\__, /_____|_| |_|\__,_|_| |_|\__, |
               __/ |                         __/ |
              |___/                         |___/

大家可以去看罗老师的写的

基础队列

双端队列

双端队列可以有队列的(从尾插入,从尾弹出)当然也可以从头插入,从头弹出。
双端队列的表示

deque<int> q;
	q.empty();///判断是否为空
    q.size();///长度
    q.front();///首元素
    q.back();///尾元素
    q.push_back();///从尾部插入
    q.push_front();///从头部插入
    q.pop_back();///将尾部元素弹出
    q.pop_front();///从头部元素弹出    

我们从双端队列的两头可进两头可出的特性得到,单调队列。—典型的题型就是“滑动窗口”.
例如:
洛谷 P1886 https://www.luogu.com.cn/problem/P1886
单调队列的板子题:
题目描述:有一个长为 n 的序列 a,以及一个大小为 k 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。
例如样例:
8 3
1 3 -1 -3 5 3 6 7
输出:
-1 -3 -3 -3 3 3
3 3 5 5 6 7


其实这道题重点就是

  • 维护长度为K的窗口。
  • 维护区间最大/最小

从复杂度来看用暴力的话复杂度就是O(n*k);根据数据范围我们很清楚他会超时,所以我们应该怎么办那,这时神奇的单调队列出现了。(主要是利用了双端队列的两头可进可出的特性)。

  • 思路就是首先看K窗口维护最大:
  • 我们要维护最大值的话就需要把区间最大值维护在或者,因为双端队列的特性,其实维护首 / 尾并没有太大的差别,怎么好像怎么来,我们那维护在为例,维护的队列中如果首元素(也就是区间最大)的序号与最后一个元素维护的序号相差不能超过K因为我们要求得是长度为K的区间,大于这个长度输入外来的,不能要。所以头过长弹出。我们正好了。因为我们想维护区间最大值,所以就是我要求的结果,这种操作统称为去头
  • 进一步引进了个问题如何维护区间的最大值?:这样想,区间最大值,那么我们就要维护一个单调递减的队列。这样就可以一直维持最大元素在首元素。在这里插入图片描述
    就像这个图一样,你要把8拿进去那么5你就得弹出来但是这个区间中最大的是10,所以10保留,那么5直接弹出就行,因为这个滑动窗口,有5的地方一定会有8,有8的话那么最大值就不会是5.这种操作统称为去尾
  • 这样就把 去 头 , 去 尾 去头,去尾 一结合那么就完美的解决了维护长度为K和维护区间最大值的两个问题。
    最小值也是按照这个思路来想,就是把维护最大值改成最小值。
/**

 _  ___             _______
| |/ (_)           |___  / |
| ' / _ _ __   __ _   / /| |__   __ _ _ __   __ _
|  < | | '_ \ / _` | / / | '_ \ / _` | '_ \ / _` |
| . \| | | | | (_| |/ /__| | | | (_| | | | | (_| |
|_|\_\_|_| |_|\__, /_____|_| |_|\__,_|_| |_|\__, |
               __/ |                         __/ |
              |___/                         |___/

**/
#include <map>
#include <queue>
#include <string>
#include<iostream>
#include<stdio.h>
#include<string.h>
#include <algorithm>
#include <bits/stdc++.h>
#include <math.h>
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
typedef pair<ll, ll> pii;
#define mem(a,x) memset(a,x,sizeof(a))
#define debug(x) cout << #x << ": " << x << endl;
#define rep(i,n) for(int i=0;i<(n);++i)
#define repi(i,a,b) for(int i=int(a);i<=(b);++i)
#define repr(i,b,a) for(int i=int(b);i>=(a);--i)
const int maxn = 1e6 + 1010;
#define inf 0x3f3f3f3f
#define sf scanf
#define pf printf
const int mod = 998244353;
const int MOD = 10007;

inline int read() {
    int x = 0;
    bool t = false;
    char ch = getchar();
    while((ch < '0' || ch > '9') && ch != '-')ch = getchar();
    if(ch == '-')t = true, ch = getchar();
    while(ch <= '9' && ch >= '0')x = x * 10 + ch - 48, ch = getchar();
    return t ? -x : x;
}

/*
vector<ll> m1;
vector<ll> m2;
priority_queue<ll , vector<ll> , greater<ll> > mn;//上  小根堆 		小到大
priority_queue<ll , vector<ll> , less<ll> > mx;//下   	大根堆  	大到小
map<ll,ll>mp;*/

deque<ll > q;
ll a[maxn], b[maxn];
#define read read()
int main() {
    /**
    q.empty();///判断是否为空
    q.size();///长度
    q.front();///首元素
    q.back();///尾元素
    q.push_back();///从尾部插入
    q.push_front();///从头部插入
    q.pop_back();///将尾部元素弹出
    q.pop_front();///从头部元素弹出
    **/
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++) {
        sf("%lld", &a[i]);
    }
    for(int i = 1; i <= n; i++) {///最小值
         ///队尾元素大于现加入的就把队尾元素弹出
        while(!q.empty() && a[q.back()] > a[i]) q.pop_back(); ///去尾
        q.push_back(i);
        if(i >= m) {
            ///长度超过m的元素弹出
            while(!q.empty() && q.front() <= i - m) { ///去头
                q.pop_front();
            }
            pf("%lld ", a[q.front()]);///输出区间最小
        }
    }
    cout << endl;
    while(!q.empty()) q.pop_front();
    for(int i = 1; i <= n; i++) {///最大值
        ///同上就是把大小反过来
        while(!q.empty() && a[q.back()] < a[i]) q.pop_back();
        q.push_back(i);
        if(i >= m) {
            while(!q.empty() && q.front() <= i - m) {
                q.pop_front();
            }
            pf("%lld ", a[q.front()]);
        }
    }
    cout << endl;

    return 0;
}

还有一种题型就是最大子段和

hdu 1003 http://acm.hdu.edu.cn/showproblem.php?pid=1003
题意很简单明了,就是求最大字段和和开始位置和结束位置。
方法一:贪心
思想就是,就算一个和,如果加上当前数 > 0 (也就是和大于原本的最大值,就更新最大值和他的左端点),如果和小于零之后,就说明前面产生了负代价,就是相当于我们结果的累赘,我们就得重新计数来算和,就把计和器清空,并更新标记初始的位置。

 /**

 _  ___             _______
| |/ (_)           |___  / |
| ' / _ _ __   __ _   / /| |__   __ _ _ __   __ _
|  < | | '_ \ / _` | / / | '_ \ / _` | '_ \ / _` |
| . \| | | | | (_| |/ /__| | | | (_| | | | | (_| |
|_|\_\_|_| |_|\__, /_____|_| |_|\__,_|_| |_|\__, |
               __/ |                         __/ |
              |___/                         |___/

**/
#include <map>
#include <stdio.h>
#include <string.h>
#include <queue>
#include <math.h>
#include <iostream>
#include <algorithm>
#include <string>
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
typedef pair<ll,ll> pii;
const int maxn=2e5+7;
const int inf =  0x7fffffff;
int main(){
    int t;
    cin>>t;
    int T=0;
    while(t--){
        int n;
        cin>>n;
        int sum=0;
        int maxx=-inf;
        int top=1; ///记录首元素的位置
        int down=1;///记录尾元素的位置
        int flag=1;///记录每次更改的首元素的位置
        for(int i=1;i<=n;i++){
            int x;
            cin>>x;
            sum+=x;
            if(sum>maxx){///和大于最大和 更新最大和 和 对应的首元素和尾元素
                maxx=sum;///最大和
                top=flag;///首地址 等于原本记录的哪个地址
                down=i;  ///尾地址 等于当前地址
            }
            if(sum<0){   /// 出现负数,说明前面产生负代价 归零
                sum=0;   /// 归零,重新计数
                flag=i+1;/// 更新标记首地址
            }
        }
         printf("Case %d:\n",++T);
        printf("%d %d %d\n", maxx,top,down);
        if(T!=t) cout << endl;
    }
    return 0;
}

方法二:DP
思路:其实中心思想还是贪心就是把记录标记的值放进DP里:推下状态转移方程吧
假设当前位置为 i i i那么他可以从两种状态转移过来
1.子段中有多个元素,他可以从前一个状态 i − 1 i-1 i1这个状态转移过来,也就会 a [ i ] + d p [ i − 1 ] a[i]+dp[i-1] a[i]+dp[i1].
2.子段中只有他自己本身,当然他也可以是他自己本身 a [ i ] a[i] a[i].
然后这两状态取最优就是状态转移方程:
d p [ i ] = m a x ( d p [ i − 1 ] + a [ i ] , a [ i ] ) dp[i] = max(dp[i-1]+a[i],a[i]) dp[i]=max(dp[i1]+a[i],a[i])

/**

 _  ___             _______
| |/ (_)           |___  / |
| ' / _ _ __   __ _   / /| |__   __ _ _ __   __ _
|  < | | '_ \ / _` | / / | '_ \ / _` | '_ \ / _` |
| . \| | | | | (_| |/ /__| | | | (_| | | | | (_| |
|_|\_\_|_| |_|\__, /_____|_| |_|\__,_|_| |_|\__, |
               __/ |                         __/ |
              |___/                         |___/

**/
#include <map>
#include <stdio.h>
#include <string.h>
#include <queue>
#include <math.h>
#include <iostream>
#include <algorithm>
#include <string>
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
typedef pair<ll,ll> pii;
const int maxn=2e5+7;
const int inf =  0x7fffffff;
int a[maxn],dp[maxn];
int main(){
    int t;
    cin>>t;
    int T=0;
    while(t--){
        int n;
        cin>>n;
        int sum=0;
        int maxx=-inf;
        int top=1; ///记录首元素的位置
        int down=1;///记录尾元素的位置
        int flag=1;///记录每次更改的首元素的位置
        for(int i=1;i<=n;i++) cin>>a[i];///输入
        ///也可以直接输入dp[i]这样就是不用a数组也行。
        maxx=a[1];///初始值为第一个元素。
        dp[1]=a[1];    ///初始赋值
        for(int i=2;i<=n;i++){
            if(dp[i-1]+a[i]>=a[i]){///如果从前一个状态转移过来的大,就直接赋值,这样不用更新初始点
                dp[i]=dp[i-1]+a[i];
            }else {/// 就是只有他本身更那一个 并且更新标记
                dp[i]=a[i];
                flag=i;
            }
            if(dp[i]>maxx){///比较大小跟贪心一样。
                maxx=dp[i];
                top=flag;
                down=i;
            }
        }
        printf("Case %d:\n",++T);
        printf("%d %d %d\n", maxx,top,down);
        if(T!=t) cout << endl;
    }
    return 0;
}

stl栈的各种操作

	stack<int> s;
	在#include<stack> 这个头文件中
	stack<int> s;///定义栈
    s.push(x);///将x放入栈首
    s.top();///返回栈首元素
    s.pop();///弹出栈首元素
    s.size();///栈长度
    s.empty();///判断栈是否为空

栈的特性就是:后进先出
可以想成一个容器,先放的在下面,要拿下面的的先把上面的拿出来,所以后进的先出

单调栈

单调栈有单调递增,还有单调递减栈。
中心思想就是比较栈首元素和加入元素的大小。


例:
洛谷 P2947 https://www.luogu.com.cn/problem/P2947
**题意:**就是给出一个 N N N ,再给你 N N N高度,让你找每个向右找第一个大于他的下标,也就是当 i < j i < j i<j时让你找 a   i   < a   j   a~i~ <a~j~ a i <a j  那么 j j 就是 i i i的答案,没有比ai大 的输出0;
**思路分析:**这个东西我们从前往后分析是不好分析的,因为我们看每一个的话,得再去他后面找答案,这样就让我们的计算变得多而繁琐,既然不能正这想那就画个思路,反着想,从后往前看,这样的话,这样想当前从后向前构造一个单调递增的栈,下标为 i i i把它与栈顶元素比较,如下图所示在这里插入图片描述
就是比较 10 10 10 9 9 9的大小,10大就把9弹出去,然后在比较,最后栈首的下标就是 i i i答案,然后把ai放入栈中,这样想,9能够办好的事10一定能办好,而且10离的近,为什么还要要9那,10都完不成的事,9更不能完成。就这样维护一个单调递增的栈。就能把这个题整出来。

/**

 _  ___             _______
| |/ (_)           |___  / |
| ' / _ _ __   __ _   / /| |__   __ _ _ __   __ _
|  < | | '_ \ / _` | / / | '_ \ / _` | '_ \ / _` |
| . \| | | | | (_| |/ /__| | | | (_| | | | | (_| |
|_|\_\_|_| |_|\__, /_____|_| |_|\__,_|_| |_|\__, |
               __/ |                         __/ |
              |___/                         |___/

**/
#include <stack>
#include <map>
#include <stdio.h>
#include <string.h>
#include <queue>
#include <math.h>
#include <iostream>
#include <algorithm>
#include <string>
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
typedef pair<ll, ll> pii;
const int maxn = 2e5 + 7;
const int inf =  0x7fffffff;
int a[maxn],b[maxn];
int main(){
	int n,m;
	cin>>n;
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	stack<int>s;
	for(int i=n;i>=1;i--){
        while(!s.empty()&&a[s.top()]<=a[i]) s.pop();///维护单调递增的栈
        if(s.empty()){///栈为空就说明,没有比他大的,答案就是0
           b[i]=0;
        }else {///否则就是她栈首元素
            b[i]=s.top();
        }
        s.push(i);///把当前元素入栈
	}
	for(int i=1;i<=n;i++){
        cout<<b[i]<<endl;
	}

	return 0;
} 

堆排序

对顶堆

加点东西:map上用pair<int ,int >;

	pii x;
	x.first=1;
	x.second=2;
	mp[x]=1;
	mp[std::pair<ll,ll>(1,2)]=1;

其实很想简单的问题让我想复杂了,就昨天蓝桥杯我用到这个地方,然后在比赛的时候整出来了,平时用的话都没整出来。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值