51nod 2500 后面第一个大于 主要方法单调栈,也讲了单调栈,要看栈的基本操作再见了

题目链接:http://www.51nod.com/Challenge/Problem.html#problemId=2500


对准备看这个的同学们说一句忠告:

1、如果你们想看这道题的枚举办法,再见~。

   我们这里没有讲这个

2、如果你连栈是啥都不知道,再见~

   我们这里也没有讲这个

3、如果你知道单调栈,不知道关系的话,欢迎加入群聊~

   你可以看,讲了

4、你知道栈,不知道单调栈,欢迎加入群聊~

      你可以看,讲了

5、如果你上面4种方式都不是,看完请评论

   请帮忙评论,说出你是什么情况

一、题目描述

小b有一个长度为n的序列t,现在她对于每个i,求最小的正数j满足 i + j ≤ n 且 t[i+j] > t[i],输出 j,如果不存在这样的 j,则输出0。

样例解释:

对于 i = 1,t[2] > t[i],所以最小的 j = 1

对于 i = 7,不存在这样的j,所以输出0

题意简化:给定序列,问序列中每个数后面第一个比它大的在哪,距离t[i]多远。

不要看上面那一大段,都没用

二、解题思路

这道题看上去只用枚举大法就可以了,实际上这道题是单调栈。

现在来科普一下什么是单调栈。要相信一开始弄了3天没弄懂的我,应该(注意是应该,不是肯定)不会让大家失望

单调栈

栈结构是一种先进后出的数据结构,单调栈是在栈的基础上实现的一种数据结构。单调栈就是栈内元素
单调递增或者单调递减的栈。

若是单调递增栈,则从栈顶到栈底的元素是严格递增的。若是单调递减栈,则从栈顶到栈底的元素是严
格递减的。

重要的部分来了~

我相信你们看上面已经看傻了,(没办法啊,这是书上的定义,得给大家看官方定义嘛)

总之,下面很重要

元素进栈过程:对于单调递增栈,若当前进栈元素为 e,从栈顶开始遍历元素,把小于e或者等于e的
元素弹出栈,直接遇到一个大于e的元素或者栈为空为止,然后再把e压入栈中。对于单调递减栈,则
每次弹出的是大于e或者等于e的元素。

明白了单调栈的加入元素的过程后,我们来看看它的性质,以及为啥要用单调栈。单调栈的一大优势就
是时间复杂度为O(n)的。

以3、4、2、6、4、5、2、3为例:

第几步 操作                                    站内元素                
1 3进栈 3
2 3出栈,4进栈 4
3 2进栈 4,2
4 2、4出栈,6进栈 6
5 4进栈 6,4
6 4出栈,5进栈 6,5
7 2进栈 6,5,2
8 2出栈 6,5,3

现在我们明白什么是单调栈了,这道题跟这个有什么关系呢?

关系来了:

因为要找的是每个数后面第一个比它大的,所以考虑倒着做。维护一个单调减的单调栈(越靠栈顶值越
小),插入当前这个数的时候不断从栈顶弹出那些小于等于它的,直到遇见一个比它大的,那么这个数
就是“它后面第一个比它大的了”。

我们不妨考虑一下,这样操作为什么是合理的。如果一个数既小又靠后,那么这个数是没有什么意义
的,因此被踢掉不会影响正确性。

到这里大家应该都明白了吧,献上代码

三、代码答案

思路一:枚举大法(不推荐,毕竟人家都讲了这么久了最后你们还是用暴力。选择这个思路会使我心情失落的!所以大家不要选)

但还是线上代码吧,作为参考,但我就懒得加注释了

#include<cstdio>
#include<iostream>
using namespace std;

int main(){
    int n, t[30010];
    bool flag = 0;
    cin >> n;
    for(int i = 1;i <= n;i++){
        cin >> t[i];
    }
    for(int i = 1;i <= n;i++){
        for(int j = 1;i+j <= n;j++){
            if(t[i+j] > t[i]){
                cout << j << ' ';
                flag = 1;
                break;
            }
            if(i+j == n){
                cout << 0 << ' ';
                flag = 1;
                break;
            }
        }
        flag = 0;
        if(flag == 0 && i == n){
            cout << 0 << ' ';
        }
    }
    return 0;
}

 

思路二(特别推荐使用,也不是“特别”吧,但是比起暴力更推荐)

这个给大家加上注释吧,毕竟肯定会有看上面看烦了直接看下面的同学,但是还是建议先看思路可能要不然看不懂

#include<stack>
#include<cstdio>
#include<iostream>
using namespace std;
long long n, a[30010], ans[30010];
stack <int> s;

/*
9
5 2 3 1 7 6 4 5 6 

8
73 74 75 71 69 72 76 73
*/ 

int main(){
    cin >> n;
    for(int i = 1;i <= n;i++){
        cin >> a[i];
    }
    for(int i = n;i >= 1;i--){//大家注意这里是从n开始的 
        //如果s里面还有数
        //并且我后面的数(上一个存在s里的)比现在这个大
        //说明现在这个的答案就是我已经存在s里的了
        //而且还说明现在这个a[i]没有用,可以踢出去 
        while(!s.empty() && a[s.top()] <= a[i]){//如果 
            s.pop();//踢出a[i] 
        }
        if(s.empty()){//如果s里面没有数字了 
            ans[i] = 0;//这个数没有答案,没有数比他大
            //所有数都被刚才淘汰掉了,或者这是a[n] 
        }else{//如果s里面还是有数字的 
            ans[i] = s.top() - i;//往答案里面存数字
            //因为题目要求我们求的是距离,所以我在s里面存的也是下标
            //只要拿上一个存在s里的数的下标-这次的下标就可以了 
        }
        s.push(i);//s里面存的是下标哦集美们  
    }
    for(int i = 1;i <= n;i++){
        cout << ans[i] << ' ';//输出答案 
    }
    return 0;
}
上一篇:NFS服务配置


下一篇:51nod 1355 斐波那契的最小公倍数