【题目链接】
ybt 1924:【03NOIP普及组】栈
洛谷 P1044 [NOIP2003 普及组] 栈
【题目考点】
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 n, a[25];
int main()
{
cin >> n;
a[0] = a[1] = 1;
for(int i = 2; i <= n; ++i)
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;
}