洛谷P1044 栈 浅谈卡特兰数


洛谷P1044


标签

  • 卡特兰数

前言


简明题意

  • 最经典的卡特兰数引入场景。给一个序列,每次可以从序列的头部入栈,或者从栈中弹出元素插入到序列的尾部,问有多少种出栈顺序

思路

  • 首先复习一下卡特兰数。卡特兰数19不爆int,35不爆longlong。其次是卡特兰数的计算方法,s(0)=1s(0)=1第一个递推式s(n)=i=0n1s(i)s(n1i)s(n)=\sum\limits_{i=0}^{n-1}s(i)*s(n-1-i),复杂度n2n^2。第二个是通项s(n)=2n!n!(n+1)!s(n)=\frac{2n!}{n!(n+1)!},阶乘20不爆longlong,12不爆int,因此用通项法最多只能求到s(10).
  • 然后讲一讲卡特兰数到底怎么推的。假设有n个数进行进出栈操作,那么我们知道这n个数有2n个进出栈次序(因为每个数都要进一次栈又要出一次栈)。现在设入栈为+1,出栈为-1,显然我们要从我们要从2n中选出n个+1,其余的地方填-1就行了,所以就有C2nnC_{2n}^n种填法。那么答案就是C2nnC_{2n}^n吗?其实不是的,看这样一个序列:-1 -1 -1 1 1 1,显然一开始栈里都没有元素怎么出栈呢?所以我们需要保证选出来的序列,对于任意k<=n,有i=1k>=0\sum_{i=1}^k>=0,这样的序列才是符合要求的。所以现在求一下不符合要求的序列,也就是求i=1k<0\sum_{i=1}^k<0的数量。我们这样考虑,对于一个不符合要求的序列例如+1 -1 -1 +1,我们从第一个元素开始直到第一次不符合要求的那个元素结束,将这些元素取相反数,这个例子中取了相反数就是-1 +1 +1 +1,现在令不符合要求的序列集合为M,取反后的序列集合为N,我们可以知道N到M是一一映射的,也就是说,集合M的大小(不符合要求的数量)等于集合N的大小,而集合N,是由n+1个+1和n-1个-1组成的,那么,集合N的大小等于C2nn1C_{2n}^{n-1},也就是不符合要求的序列数量是C2nn1C_{2n}^{n-1},而总的数量是C2nnC_{2n}^n,所以符合要求的数量是:C2nnC2nn1C_{2n}^n-C_{2n}^{n-1}
  • 然后我们可以更深一步,探讨一下卡特兰数的变形。回忆之前的情形,+1和-1的数量是相等的,下一种情形,+1和-1的数量不相等。我们简化一下题目,就不引入情景了。现在有n个+1,m个-1,要使得前缀和永远>=0,问有多少种排列方法。这就需要引入集合方法,我们建立一个n*m的表格
    在这里插入图片描述
    +1往右,-1往上,那么为了满足要求,-1的数量在任意时刻不能多余+1的数量,也就是往上的线不能超过中线。我们先举一个犯规的例子:
    在这里插入图片描述我们仍然建立一个像普通卡特兰数的映射,将第一次超过中线的数以及之前的所有取反,然后将剩余的平移过来:
    在这里插入图片描述
    我们发现,又和卡特兰数的普通情形一样了,+1多了一次选择,-1少了一次选择,于是犯规的数目就是Cn+mn1C_{n+m}^{n-1},所以符合要求的数目是:Cn+mnCn+mn1C_{n+m}^{n}-C_{n+m}^{n-1}.而这种变式的一个经典问题是硬币找零问题,又n个人有100元,m个人有50元,他们排队去买东西,而卖家没有硬币找零,只能用收的钱找零,问有多少种方案可以使得卖家正常收款。我认为,100和50其实是一一对应的关系,也就是+1和-1的关系,那么是不是还可以这样变式呢?给定n个+2和m个-1,问有多少种排列方法使得前缀和恒>=0?处理方法其实和上面的类似。
  • 再看下一个问题,圆内连弦问题。在一个圆上取2n个点,连出n条弦,问使得这n条弦互不相交的方案数。为了解决这个问题,我们先引入一个较为简单的例子,现在有n个+1 n个-1,需要将他们排列在圆上,使得顺时针相加起来任意时刻和都>=0,问有多少种排列方法。这其实和序列上的问题没什么区别。那么现在在符合刚刚要求的排列上,直接将相邻的+1和-1连起来(连好后两侧的就相邻),发现刚好是所有弦不相交的。所以连弦问题显然就是卡特兰数。
  • 最后还有一个叫卡特兰问题,实际上就是凸多边形的剖分,n边形用n-3条边分成三角形的方案数就是卡特兰数。为了证明这个,需要两条引理,1.n对括号组成的合法方案数是C(n) 2.n个数连乘的顺序有C(n-1)种。括号的很显然,而第二条引理,实际上是和括号问题建立了一个一一映射的关系,从而得出了结论。具体是n个数,那么取n-1对括号,对于每一种合法的括号方案,我们设置一个指针指向n个数中的第一个元素,然后顺序考虑我们的合法括号序列,遇到左括号,则指针右移一位,遇到右括号,则指针指向的数与其左边一个数乘起来,将乘出来的结果看成一个数,然后进行完这个过程,所以每种连乘方案和合法括号序列又建立了映射,因此n个数的连乘方案是C(n-1)。而卡特兰问题就使用了这两个引理。感兴趣的朋友可以参考卡特兰数 — 计数的映射方法的伟大胜利

注意事项


总结


AC代码

#include<cstdio>
#include<string>
#include<queue>
#include<iostream>
#include<vector>
#include<cmath>
#include<algorithm>
using namespace std;

const int maxn = 300 + 10;
const int dir[8][2] = { 1, 2, 1, -2, -1, 2, -1, -2, 2, 1, 2, -1, -2, 1, -2, -1 };

void solve()
{
	int n;
	scanf("%d", &n);
	long long dp[100] = { 0 };
	dp[0] = 1;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 0; j <= i - 1; j++)
			dp[i] += dp[j] * dp[i - j - 1];
		
	}

	printf("%lld\n", dp[n]);
}

void test()
{

}

int main()
{
	freopen("Testin.txt", "r", stdin);
	solve();
	//test();
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章