前言
开头以为是个暴力搜索的过程,本来想问递归;但是回想一下,感觉递归来做好像时间复杂度会很高,因为有重叠的子问题在里面,然后就想起来用动态规划来做。推导完DP表达式以后发现这是卡特兰数。
题目来源:P1044 [NOIP2003 普及组] 栈
题意理解:
本题的题目重点不是栈,而是借着栈来表达一种做法。初始一共分为三个部分,输入序列,中间的栈以及输出序列。输入序列上是1-n这n个数字的排列,然后这n个数字可以进入入栈操作;入栈后的数字可以进行出栈到输出序列上的操作。当输入序列和栈皆为空的时候,输出序列就形成了1-n这n个数字的一个新的排列。现在题目要求输入序列数字总数为n的时候输出序列能够形成排列的总个数。
题目解答:
乍一看的递归搜索,因为对应这样的状态转移关系:(1)当栈不空的时候,若输入序列为空则只能进行数字出栈操作,反之既可以进行入栈操作又可以进行出栈操作(2)当栈空的时候,若输入序列不空则只能进行入栈操作,反之则进行结算。理论上利用递归来做没问题;但是当输入数据规模n很大的时候,空间和事件开销都很大。
注意到,假设n=4,则对于输入序列1 2 3 4,初始1入栈以后,很容易发现,如果1此时出栈,那么问题转化为2 3 4求输出序列个数,而这又和对1 2 3求输出序列个数一致。而当1不出栈,而是等2入栈,由于栈的特性,1一定在2之后出现,此时,若2出栈,1也出栈(即1紧跟2之后)则问题也跟求1 2的输出序列一样了…这样,记
a
i
a_i
ai为输入序列为1,2,3,…i时输出序列的总个数,那么对于
a
n
a_n
an,假设
a
1
,
a
2
,
.
.
.
a
n
−
1
a_1,a_2,...a_{n-1}
a1,a2,...an−1已知,同时假设
a
0
a_0
a0为1(至于为啥
a
0
a_0
a0是1可以自己想,主要还是可以统一表达式),那么当输出序列中以1开头,则一共有
1
∗
a
n
−
1
=
a
0
∗
a
n
−
1
1*a_{n-1}=a_0*a_{n-1}
1∗an−1=a0∗an−1种不同情况;当输出序列以2,3被1截断做划分,则前者只有一个2,对应
a
1
a_1
a1种情况,后者从3到n一共n-2个数,对应输出序列有
a
n
−
2
a_{n-2}
an−2种,一共是
a
1
∗
a
n
−
2
a_1*a_{n-2}
a1∗an−2;当输出序列以3,4为截断,即1在2,3都到输出序列后再到输出序列上,则前者有
a
2
a_2
a2中情况,后者为
a
n
−
3
a_{n-3}
an−3种情况,一共是
a
2
∗
a
n
−
3
种
a_2*a_{n-3}种
a2∗an−3种;… …;直到 2 … n全都到输出序列后再将1出栈,则有
a
n
−
1
∗
1
=
a
n
−
1
∗
a
0
a_{n-1}*1=a_{n-1}*a_0
an−1∗1=an−1∗a0中情况,将它们都加起来则得到如下递推表达式:
a
n
=
a
0
∗
a
n
−
1
+
a
1
∗
a
n
−
2
+
a
2
∗
a
n
−
3
+
.
.
.
+
a
1
∗
a
n
−
2
+
a
0
∗
a
n
−
1
a_n=a_0*a_{n-1}+a_1*a_{n-2}+a_2*a_{n-3}+...+a_1*a_{n-2}+a_0*a_{n-1}
an=a0∗an−1+a1∗an−2+a2∗an−3+...+a1∗an−2+a0∗an−1
这样初始化
a
0
a_0
a0然后一直DP求到
a
n
a_n
an就可以了。下面给出我的AC代码:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
int n,i,j;
scanf("%d",&n);
int a[n+1];
for(i=0;i<=n;i++){
if(i==0)
a[i]=1;
else
a[i]=0;
}
for(i=1;i<=n;i++){
for(j=0;j<=i-1;j++)
a[i]+=a[j]*a[i-1-j];
}
printf("%d",a[n]);
return 0;
}