一、深搜
别的也不会,一个深搜走天下!深搜我们主要关心的是下一步噢~
怎么个深搜法呢?我们模拟一下,有一个装个顺序号小球的队列,一个个准备放到一个栈里。一共几下面几种场景:
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;
}