信息学奥赛一本通 1924:【03NOIP普及组】栈 | 洛谷 P1044 [NOIP2003 普及组] 栈

【题目链接】

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是数列中第几个数字lr
10n-1
21n-2
n-1n-21
nn-10

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=0n1a[l]a[n1l]
该公式即为卡特兰数的递推公式。

解法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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值