洛谷 P1044 [NOIP2003 普及组] 栈

[NOIP2003 普及组] 栈 - 洛谷

蒟蒻小学生又回来了

最近要学栈的算法了,所以写篇题解

题目背景

栈是计算机中经典的数据结构,简单的说,栈就是限制在一端进行插入删除操作的线性表。

栈有两种最重要的操作,即 pop(从栈顶弹出一个元素)和 push(将一个元素进栈)。

栈的重要性不言自明,任何一门数据结构的课程都会介绍栈。宁宁同学在复习栈的基本概念时,想到了一个书上没有讲过的问题,而他自己无法给出答案,所以需要你的帮忙。

题目描述

宁宁考虑的是这样一个问题:一个操作数序列,1,2,…,n1,2,…,n(图示为 1 到 3 的情况),栈 A 的深度大于 nn。

现在可以进行两种操作,

  1. 将一个数,从操作数序列的头端移到栈的头端(对应数据结构栈的 push 操作)
  2. 将一个数,从栈的头端移到输出序列的尾端(对应数据结构栈的 pop 操作)

使用这两种操作,由一个操作数序列就可以得到一系列的输出序列,下图所示为由 1 2 3 生成序列 2 3 1 的过程。

(原始状态如上图所示)

你的程序将对给定的 nn,计算并输出由操作数序列 1,2,…,n1,2,…,n 经过操作可能得到的输出序列的总数。

输入格式

输入文件只含一个整数 nn(1≤n≤181≤n≤18)。

输出格式

输出文件只有一行,即可能输出序列的总数目。

输入输出样例

输入 #1

3

输出 #1

5

说明/提示

【题目来源】

NOIP 2003 普及组第三题


首先,栈是什么?是算法,还是结构?

给出的答案->栈(Stack)是一种线性存储结构

栈又是以一个什么样的过程来储存的呢,以下是来自小学生的手写稿

c++还给出了其它的快捷语言

#include< stack >//头文件
stack< int > s;//定义一个名为s的栈,其内部为int类型
s.empty();         //如果栈为空则返回true, 否则返回false;
s.size();          //返回栈中元素的个数
s.top();           //返回栈顶元素, 但不删除该元素
s.pop();           //弹出栈顶元素, 但不返回其值
s.push();          //将元素压入栈顶

你理解了吗???


言归正传

【题目考点】
1. 递推、递归
2. 栈
3. 打表
4. 卡特兰数
【解题思路】
解法1:一维递推

    设数组a,a[i]表示i个数组成的数列经过栈处理后得到的数列总数

    易知:a[1] = 1。特殊地,将a[0]也设为1。

    1~n组成数列,要经过栈处理后形成一个数列,要经历以下几个阶段。
        1入栈
        l个数字入栈出栈,经过栈处理形成数列1。(0 <= l <= n-1)
        1出栈
        r个数字入栈出栈,经过栈处理形成数列2。(0 <= r <= n-1)

    其中l + r + 1 = n,最后得到的数列为:数列1,1,数列2

    由l个数字经过栈处理形成数列1,种类数为a[l],同理,由r个数字组成的数列2的种类数为a[r]。则类似"数列1,1,数列2"的数列的种类数为:a[l]*a[r]。

    1在最终数列中若是第1个数字,此时l为0, r为n-1。1在最终数列中若是第2个数字,此时l为1,r为n-2。将相关量列成表格,有:

1是数列中第几个数字    l    r
1    0    n-1
2    1    n-2
…    …    …
n-1    n-2    1
n    n-1    0

1在每个位置时,最终数列的种类数为:a[l]*a[r]。已知r = n - 1 - l,即为a[l]*a[n-1-l]
每种数列的种类数加和即为n个数字经过栈处理后得到的数列种类数,其值为:
a [ n ] = ∑ l = 0 n − 1 a [ l ] ∗ a [ n − 1 − l ] a[n] =\sum_{l=0}^{n-1}a[l]*a[n-1-l] a[n]=∑l=0n−1​a[l]∗a[n−1−l]
该公式即为卡特兰数的递推公式。
解法2:二维递推

设递推状态a[i][j]为:当操作数序列有i个数字,栈中有j个数字时,可以形成的序列数目。

    当操作数序列为空时,没有数字可以入栈,只能进行出栈动作,此时可以形成的序列只有1种。即i为0时,a[i][j]=1。
    当栈为空时,只能进行入栈操作,所以有:当j为0时,a[i][j] = a[i-1][1];
    当操作数有i个,栈中有j个数时,可以有两种操作:
    操作1: 将一个数字从操作数序列中取出,进栈,这样做后,操作数剩余i-1个,栈中有j+1个数,可以形成a[i-1][j+1]种序列。
    操作2: 栈中栈顶数字出栈。这样做后,操作数剩余i个,栈中有j-1个数,可以形成a[i][j-1]种序列
    所以有:a[i][j] = a[i-1][j+1] + a[i][j-1];

解法3:记忆化递归

思路与上述递推大体相同,用记忆化递归来做。
解法4:模拟+搜索+打表

由于输入数据数量十分有限(1~18),因此可以进行打表。
根据题目描述,在打表程序中,用搜索的写法模拟一个序列入栈和出栈的过程,将结果输出到文件或拷贝控制台的输出结果。打表程序大约会运行30秒到1分钟。
将打表得到的结果复制到题解程序中,作为数组的初值。题解程序直接查询数组的值即可。

解法1:一维递推
#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n, a[25];
    cin>>n;
    a[0] = 1;
    a[1] = 1;
    for(int i = 2; i <= n; ++i)
    {
        a[i] = 0;
        for(int l = 0; l <= i - 1; ++l)
            a[i] += a[l] * a[i - 1 - l];
    }
    cout<<a[n];
    return 0;
}

解法2:二维递推
#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n, a[20][20];//a[i][j]:当操作数序列有i个数字,栈中有j个数字时,可以形成的序列数目。
    cin>>n;
    for(int i = 0; i <= n; ++i)
        for(int j = 0; j <= n; ++j)
        {
            if(i == 0)
                a[i][j] = 1;
            else if(j == 0)
                a[i][j] = a[i-1][1];
            else
                a[i][j] = a[i-1][j+1] + a[i][j-1];
                
        }
    cout<<a[n][0];
    return 0;
}
解法3:记忆化递归
#include<bits/stdc++.h>
using namespace std;
int f[20][20]; //f[i][j]:当操作数序列有i个数字,栈中有j个数字时,可以形成的序列数目。
int seqNum(int i, int j)//求f[i][j]
{
    if(f[i][j] == 0)//如果没求出过f[i][j],则求一下 
    {
        if(i == 0)
            f[i][j] = 1;
        else if(j == 0)
            f[i][j] = seqNum(i-1, 1);
        else
            f[i][j] = seqNum(i-1, j+1) + seqNum(i, j-1);
    }
    return f[i][j];
}
int main()
{
    int n;
    cin>>n;
    cout<<seqNum(n, 0);
    return 0;
}
解法4:模拟+搜索+打表
  • 打表程序(运行30秒到1分钟)
  • #include<bits/stdc++.h>
    using namespace std;
    int ct, n;
    stack<int> stk;
    void dfs(int k)
    {
    	if(k > n)
    	{
    		ct++;
    		return;
    	}
    	if(stk.empty() == false)
    	{
    		int top = stk.top();
    		stk.pop();
    		dfs(k);
    		stk.push(top);
    	}
    	stk.push(k);
    	dfs(k+1);
    	stk.pop();
    }
    int main()
    {
    	bool isFirst = true;
    	for(n = 0; n <= 18; ++n)
    	{
    		stk = stack<int>();
    		ct = 0;
    		dfs(1);
    		if(isFirst)
    			isFirst = false;
    		else
    			cout << ',';
    		cout << ct;
    	}
    	return 0;
    }
    

    题解程序

    #include<bits/stdc++.h>
    using namespace std;
    int main()
    {
    	int n, ans[20] = {1,1,2,5,14,42,132,429,1430,4862,16796,58786,208012,742900,2674440,9694845,35357670,129644790,477638700};
    	cin >> n;
    	cout << ans[n];
    	return 0;
    }
    

    ok又AC了一道题

  • 祝大家早日AC!

  • 24
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值