蒟蒻小学生又回来了
最近要学栈的算法了,所以写篇题解
题目背景
栈是计算机中经典的数据结构,简单的说,栈就是限制在一端进行插入删除操作的线性表。
栈有两种最重要的操作,即 pop(从栈顶弹出一个元素)和 push(将一个元素进栈)。
栈的重要性不言自明,任何一门数据结构的课程都会介绍栈。宁宁同学在复习栈的基本概念时,想到了一个书上没有讲过的问题,而他自己无法给出答案,所以需要你的帮忙。
题目描述
宁宁考虑的是这样一个问题:一个操作数序列,1,2,…,n1,2,…,n(图示为 1 到 3 的情况),栈 A 的深度大于 nn。
现在可以进行两种操作,
- 将一个数,从操作数序列的头端移到栈的头端(对应数据结构栈的 push 操作)
- 将一个数,从栈的头端移到输出序列的尾端(对应数据结构栈的 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−1a[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!