《算法竞赛进阶指南》之栈篇(包含min函数的栈+编辑器+火车进栈+直方图中最大的矩形)

《算法竞赛进阶指南》续篇~

文章目录

    • 包含min函数的栈
      • 解题思路
      • AC代码
    • 编辑器
      • 解题思路
      • AC代码
    • 火车进栈
      • 题目大意
      • 解题思路
      • AC代码
    • 直方图中最大的矩形
      • 解题思路
      • AC代码

包含min函数的栈

原题链接:
https://www.acwing.com/problem/content/90/

题目描述
设计一个支持push,pop,top等操作并且可以在O(1)时间内检索出最小元素的堆栈。

push(x)–将元素x插入栈中
pop()–移除栈顶元素
top()–得到栈顶元素
getMin()–得到栈中最小元素

样例
MinStack minStack = new MinStack();
minStack.push(-1);
minStack.push(3);
minStack.push(-4);
minStack.getMin(); --> Returns -4.
minStack.pop();
minStack.top(); --> Returns 3.
minStack.getMin(); --> Returns -1.

解题思路

开两个栈,一个存原栈内容,一个存最小栈,然后进行栈的具体操作
问题就在于如何维护最小栈。

例如栈:4 5 6 3 8 9 1 2
最小栈:4 4 4 3 3 3 1 1

我们可以发现维护最小栈的条件是:当i>j且stack[i]>stack[j],那么将stack[j]加入到min_stack[j]里面,否则min_stack[j]=stack[i].

AC代码

class MinStack {
public:

    stack<int> stk,min_stk;
    
    /** initialize your data structure here. */
    MinStack() {
        
    }
    
    void push(int x) {
        stk.push(x);
        if(min_stk.empty()||x<=min_stk.top()) min_stk.push(x);
    }
    
    void pop() {
        if(stk.top()==min_stk.top()) min_stk.pop();
        stk.pop();
    }
    
    int top() {
        return stk.top();
    }
    
    int getMin() {
        return min_stk.top();
    }
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

编辑器

原题链接:
https://www.acwing.com/problem/content/130/

题目描述
你将要实现一个功能强大的整数序列编辑器。

在开始时,序列是空的。

编辑器共有五种指令,如下:

1、I x,在光标处插入数值 x。
2、D,将光标前面的第一个元素删除,如果前面没有元素,则忽略此操作。
3、L,将光标向左移动,跳过一个元素,如果左边没有元素,则忽略此操作。
4、R,将光标向右移动,跳过一个元素,如果右边没有元素,则忽略次操作。
5、Q k,假设此刻光标之前的序列为 a1,a2,…,an,输出 max1≤i≤kSi,其中 Si=a1+a2+…+ai。

输入格式
第一行包含一个整数 Q,表示指令的总数。

接下来 Q 行,每行一个指令,具体指令格式如题目描述。

输出格式
每一个 Q k 指令,输出一个整数作为结果,每个结果占一行。

数据范围
1≤Q≤106,
|x|≤103,
1≤k≤n
输入样例:
8
I 2
I -1
I 1
Q 3
L
D
R
Q 2
输出样例:
2
3

解题思路

这是一道对顶栈问题。什么意思?
仔细分析一下一下题目,这道题的所有操作都是围绕着光标进行的,光标的左移右移和删除,完全可以看作是两个对顶栈的相互入栈和出栈,如图所示:
《算法竞赛进阶指南》之栈篇(包含min函数的栈+编辑器+火车进栈+直方图中最大的矩形)_第1张图片
对于第五个操作,就是求从1到光标处每一个值的前缀和的最大值是多少,每次操作后记录每个下标的最大前缀和就可以了,按题意,我们只需要求左边的栈就好了。

AC代码

#include
#include
#include
using namespace std;
const int N=1000200;

int strl[N],strr[N],fl,fr;
int sum[N],f[N];

//对顶栈的移位操作
void push_stack(int x){
    strl[++fl] = x;
    sum[fl]=sum[fl-1]+x;
    //求最大前缀和
    f[fl]=max(f[fl-1],sum[fl]);
}

int main(){
    int n;
    cin>>n;
    //f[0]一定要置为int类型的最小值,这样在找最小前缀和的时候如果是负数的情况才不会出错.否则,因为默认f[0]=0,会输出0
    f[0]=INT_MIN;
    while(n--){
        char s[2];
        scanf("%s",s);
        int x;
        if(*s=='I'){
            cin>>x;
            push_stack(x);
        }
        else if(*s=='D'){
            if(fl>0) fl--;
            
        }
        else if(*s=='L'){
            if(fl>0) strr[++fr]=strl[fl--];
        }
        else if(*s=='R'){
            if(fr>0) push_stack(strr[fr]),fr--;
        }
        else{
            cin>>x;
            cout<<f[x]<<endl;
        }
    }
    return 0;
}

火车进栈

原题链接:
https://www.acwing.com/problem/content/131/

题目描述
这里有 n 列火车将要进站再出站,但是,每列火车只有 1 节,那就是车头。

这 n 列火车按 1 到 n 的顺序从东方左转进站,这个车站是南北方向的,它虽然无限长,只可惜是一个死胡同,而且站台只有一条股道,火车只能倒着从西方出去,而且每列火车必须进站,先进后出。

也就是说这个火车站其实就相当于一个栈,每次可以让右侧头火车进栈,或者让栈顶火车出站。

车站示意如图:

        出站<——    <——进站
                 |车|
                 |站|
                 |__|

现在请你按《字典序》输出前 20 种可能的出栈方案。

输入格式
输入一个整数 n,代表火车数量。

输出格式
按照《字典序》输出前 20 种答案,每行一种,不要空格。

数据范围
1≤n≤20
输入样例:
3
输出样例:
123
132
213
231
321

题目大意

输出 1 、 2 、 . . . . . . 、 n {1、2、......、n} 12......n的按字典序前20个出栈情况

解题思路

模拟栈的出栈情况,对于每一次栈有两个选择,要么进栈,要么出栈。
由此,我们可以知道我们需要三个变量:可以用stack3来表示入栈的数值,每入一次栈就加一;用stack2表示栈中的情况,当栈不为空的时候才可以出栈;stack1来表示出栈情况
我们可以通过递归来实现整个过程:

1.当stack1的长度为n时,那么达到边界
2.当stack2不为空的时候出栈
3.入栈

注意:每次操作后记得还原栈的情况
《算法竞赛进阶指南》之栈篇(包含min函数的栈+编辑器+火车进栈+直方图中最大的矩形)_第2张图片

AC代码

#include
#include
#include
using namespace std;

int n;
int cnt = 20,stack3;
vector<int> stack1;
stack<int> stack2;

void dfs(){
    if(!cnt) return ;
    //边界条件
    if(stack1.size()==n){
        cnt--;
        for(int i=0;i<n;i++) cout<<stack1[i];
        cout<<endl;
        return ;
    }
    
    //出栈
    if(!stack2.empty()){
        stack1.push_back(stack2.top());
        stack2.pop();
        dfs();
        stack2.push(stack1.back());
        stack1.pop_back();
    }
    
    //入栈
    if(stack3<n){
    stack3++;
    stack2.push(stack3);
    dfs();
    stack2.pop();
    stack3--;
    }
    return;
}

int main(){
    cin>>n;
    dfs();
    return 0;
}

直方图中最大的矩形

原题链接:
https://www.acwing.com/problem/content/133/

题目描述
直方图是由在公共基线处对齐的一系列矩形组成的多边形。

矩形具有相等的宽度,但可以具有不同的高度。

例如,图例左侧显示了由高度为 2,1,4,5,1,3,3 的矩形组成的直方图,矩形的宽度都为 1:

《算法竞赛进阶指南》之栈篇(包含min函数的栈+编辑器+火车进栈+直方图中最大的矩形)_第3张图片

通常,直方图用于表示离散分布,例如,文本中字符的频率。

现在,请你计算在公共基线处对齐的直方图中最大矩形的面积。

图例右图显示了所描绘直方图的最大对齐矩形。

输入格式
输入包含几个测试用例。

每个测试用例占据一行,用以描述一个直方图,并以整数 n 开始,表示组成直方图的矩形数目。

然后跟随 n 个整数 h1,…,hn。

这些数字以从左到右的顺序表示直方图的各个矩形的高度。

每个矩形的宽度为 1。

同行数字用空格隔开。

当输入用例为 n=0 时,结束输入,且该用例不用考虑。

输出格式
对于每一个测试用例,输出一个整数,代表指定直方图中最大矩形的区域面积。

每个数据占一行。

请注意,此矩形必须在公共基线处对齐。

数据范围
1≤n≤100000,
0≤hi≤1000000000
输入样例:
7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0
输出样例:
8
4000

解题思路

对于第i个直方体,当它在扩展时,遇到左边第一个比它小的直方体l[i]停止,同理遇到右边第一个比它小的直方体r[i]则停止。l[i]、r[i]存的是左右第一个最小直方体的下标。那么可以得到它的面积就是(l[i]-1)-(r[i]-1)+1=l[i]-r[i]-1
那么问题就是,如何求左右两边最小的直方体。
这其实是数据结构中一个单调栈的问题。
关于单调栈学习记录的博客:https://blog.csdn.net/jigsaw_zyx/article/details/119825407
那么只要我们分别求出左右两边的单调栈就可以了

AC代码

#include
#include
#include
#include

using namespace std;
int n;
const int N=100200;
//l[N],r[N],q[N]存的都是下标
int h[N],l[N],r[N],q[N];
typedef long long LL;

int main(){
    while(~scanf("%d",&n)){
      if(n==0) break;
        for(int i=1;i<=n;i++) scanf("%d",&h[i]);
        //这里一定要置为-1
        h[0]=h[n+1]=-1;

        //左边的第一个比它小的数
        int t=0;
        q[0]=0;
        for(int i=1;i<=n;i++){
            while(h[i]<=h[q[t]]) t--;
            l[i]=q[t];
            q[++t]=i;
        }

        //右边的第一个比它小的数
        t=0;
        q[0]=n+1;
        for(int i=n;i;i--){
            while(h[i]<=h[q[t]]) t--;
            r[i]=q[t];
            q[++t]=i;
        }


        LL res=0;
        for(int i=1;i<=n;i++) res = max(res,(LL)h[i]*(r[i]-l[i]-1));
        printf("%lld\n",res);

    }
    return 0;
}

你可能感兴趣的:(算法竞赛进阶指南,数据结构,算法,编辑器)