P1044 栈题解

题目传递门

一、深搜

别的也不会,一个深搜走天下!深搜我们主要关心的是下一步噢~

怎么个深搜法呢?我们模拟一下,有一个装个顺序号小球的队列,一个个准备放到一个栈里。一共几下面几种场景:
1、队列为空,栈为空。
这种场景的下一步就只能是“游戏终止”,而“游戏终止”时我们应该方案数+1。

2、队列为空,栈不空。
这种场景下下一步只能是栈弹出一个,一直到弹空了为止,也就是,方案数+1。

3、队列不空,栈为空。
这种场景下下一步只能是队列出来一个,栈进去一个,场景变为“队列-1,栈+1”。

4、队列不空,栈不空。
这种场景下下一步有点麻烦,可以是队列-1,栈+1;也可以是队列不动,栈-1。

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;

int n;
// i 表示队列里还有几个待排的数,
// j 表示栈里有 j 个数,dfs(i,j)表示此时的情况数
// 不重不漏的描述了所有情况

LL dfs(int i, int j) {
    LL ans = 0;
    //(1)队列空,栈空
    if (i == 0 && j == 0)ans += 1; //这是递归出口,增加一种方法
    //(2)队列空,栈不空
    if (i == 0 && j > 0) ans += 1; //只能一个个蹦出去啦~
    //(3)队列不空,栈空
    if (i > 0 && j == 0) ans += dfs(i - 1, j + 1); //从队列中取一个,放入到栈中
    //(4)队列不空,栈不空
    if (i > 0 && j > 0) ans += dfs(i, j - 1) + dfs(i - 1, j + 1);//有两种选择,一是队列不动,栈中出去一个;另一种是队列取一个,放入栈中
    return ans;
}

int main() {
    cin >> n;
    printf("%lld", dfs(n, 0));
    return 0;
}

结果5个测试点,过了4个,最后一个TLE了~

2、记忆化搜索

我们观察到,上面的代码之所以超时,根源是因为这个和前面“过河卒”一样,存在大量重复计算,到达某个点后,这个点再出发都是重复的。可以考虑使用反向思维用递推!当然,本题也可以使用记化搜索来解决:

#include <bits/stdc++.h>

using namespace std;
const int N = 20;
typedef long long LL;

int n;
// 定义一个二维数组f[i,j]f[i,j],用下标 i 表示队列里还有几个待排的数,
// j 表示栈里有 j 个数,f[i,j]表示此时的情况数
// 不重不漏的描述了所有情况
LL f[N][N]; //记忆化搜索

LL dfs(int i, int j) {
    //算过
    if (f[i][j]) return f[i][j];
    //(1)队列空,栈空
    if (i == 0 && j == 0) f[i][j] += 1; //这是递归出口,增加一种方法
    //(2)队列空,栈不空
    if (i == 0 && j > 0) f[i][j] += 1;//只能一个个蹦出去啦~
    //(3)队列不空,栈空
    if (i > 0 && j == 0) f[i][j] += dfs(i - 1, j + 1);//从队列中取一个,放入到栈中
    //(4)队列不空,栈不空
    if (i > 0 && j > 0) f[i][j] += dfs(i, j - 1) + dfs(i - 1, j + 1);//有两种选择,一是队列不动,栈中出去一个;另一种是队列取一个,放入栈中
    return f[i][j];
}

int main() {
    cin >> n;
    printf("%lld", dfs(n, 0));
    return 0;
}

使用记化搜索后,成功AC本题。

3、递推

既然可以使用递推,那么递推的步骤和思路是什么样的呢?
1、边界值
一定要有小的方向的边界!!!而且特别要注意元素个数是0的情况!!!

2、求啥就定义啥,按需求定义数组
(dp[i])代表了(i)个元素的所有出管方法数量。

3、对(i)个元素的大场景进行子集合的切分,要不重不漏,一般是按最后一个不相同的点切分。
本题是按最后一个出栈元素进行划分开,分情况讨论。

对于从(j in [1 , i])来讲,第(j)个小球是最后一个出栈的,那么在它之前有(j-1)个元素先入栈,先出栈;在它之后,有(n-j)个小球后入栈,后出栈。那么,以它为最后一个出栈的方案数量就是(dp[i]=dp[j-1] imes dp[i-j])(乘法原理)
对于(i)个小球来讲,就是如下的伪代码示意:

  for(int j=1;j<=i;j++)
     dp[i]+=dp[j-1]*dp[i-j];//所有子集合的方案数量和,加法原理

完整代码

#include <bits/stdc++.h>

using namespace std;
// h[i]:i个元素一共有h[i]种出管方式
// 0个元素,只有一种情况,这种情况就是啥也不出,啥也不出也算是一种场景。
// 1个元素,只有一种情况,就是出队列,进栈,出栈。
int n, dp[20] = {1, 1};


int main() {
    cin >> n;

    for (int i = 2; i <= n; i++)
        for (int j = 1; j <= i; j++)//最后出栈的元素假设是j
            dp[i] += dp[j-1] * dp[i - j];//所有的可能性加在一起

    printf("%d", dp[n]);
    return 0;
}

4、卡特兰数解法

为什么会想到用卡特兰数公式来解这道题呢?
超棒的讲解
https://www.bilibili.com/video/BV1nE411A7ST?from=search&seid=2618099886973795159

#include <bits/stdc++.h>

// 超棒的讲解
// https://www.bilibili.com/video/BV1nE411A7ST?from=search&seid=2618099886973795159
using namespace std;

/**
如果n=1 1
如果n=2 2
如果n=3 5
如果n=4 14
如果n=5 42
如果n=6 132
如果n=7 429
如果n=8 1430
如果n=9 4862
如果n=10 16796
如果n=11 58786
如果n=12 208012
如果n=13 742900
如果n=14 2674440
如果n=15 9694845
如果n=16 35357670
如果n=17 129644790
如果n=18 477638700
卡特兰数!!!
 */
int catalan(int n) {
    if (n == 0 || n == 1) return 1;
    int res = 0;
    for (int i = 1; i <= n; i++) res += catalan(i - 1) * catalan(n - i);
    return res;
}

int main() {
    int n;
    cin >> n;
    cout << catalan(n) << endl;
    return 0;
}
原文地址:https://www.cnblogs.com/littlehb/p/15019135.html