Train Problem II
Time Limit: 2000/1000 MS (Java/Others)
Memory Limit: 65536/32768 K (Java/Others)
Problem Description
As we all know the Train Problem I, the boss of the Ignatius Train Station want to know if all the trains come in strict-increasing order, how many orders that all the trains can get out of the railway.
Input
The input contains several test cases. Each test cases consists of a number N(1<=N<=100). The input is terminated by the end of file.
Output
For each test case, you should output how many ways that all the trains can get out of the railway.
Sample Input
1
2
3
10
Sample Output
1
2
5
16796
Hint
The result will be very large, so you may not process it by 32-bit integers.
Reference Code
#include<bits/stdc++.h>
using namespace std;
struct BigInteger{
static const int BASE=100000000;
static const int WIDTH=8;
vector<int> s;
BigInteger (long long num=0){
*this=num;
}
BigInteger operator=(long long num){
s.clear();
do{
s.push_back(num%BASE);
num/=BASE;
}while (num>0);
return *this;
}
BigInteger operator+(const BigInteger &b)const{
BigInteger c;
c.s.clear();
for (int i=0,g=0;;i++){
if (!g&&i>=s.size()&&i>=b.s.size())
break;
int x=g;
if (i<s.size()) x+=s[i];
if (i<b.s.size()) x+=b.s[i];
c.s.push_back(x%BASE);
g=x/BASE;
}
return c;
}
BigInteger operator+=(const BigInteger &b){
return *this=*this+b;
}
bool operator<(const BigInteger &b)const{
if (s.size()!=b.s.size())
return s.size()<b.s.size();
for (int i=s.size()-1;i>=0;i--)
if (s[i]!=b.s[i])
return s[i]<b.s[i];
return false;
}
bool operator!=(const BigInteger &b)const{
return b<*this||*this<b;
}
};
ostream& operator<<(ostream &out,const BigInteger &x){
out<<x.s.back();
for (int i=x.s.size()-2;i>=0;i--){
char buf[20];
sprintf(buf,"%08d",x.s[i]);
for (int j=0;j<strlen(buf);j++)
out<<buf[j];
}
return out;
}
const int maxn=100+3;
int n;
BigInteger rem[maxn][maxn];
BigInteger dfs(int x,int y){
if (rem[x][y]!=0) return rem[x][y];
BigInteger res=0;
for (int k=max(-x,-1);k<y;k++)
res+=dfs(x+k,y-k-1);
return rem[x][y]=res;
}
int main(){
rem[0][0]=1;
while (~scanf("%d",&n)){
cout<<dfs(0,n)<<endl;
}
return 0;
}
Tips
由于我想法比较简单,就是构造了一个状态,然后直接动规+深搜+大整数类做了,也AC了。但是发现时间比其他人的慢(124MS),代码长度也更长,说明还有更好的方法。
先来说一说我的想法:
我们可以构造一个一行两列的状态矩阵
[
x
,
y
]
\left[ x,y \right]
[x,y]表示在当前情况下,已进栈的有
x
x
x个元素,未进栈的有
y
y
y个元素,即还剩下
x
+
y
x+y
x+y个元素未出栈。
在下一个状态,若先有
k
k
k个元素进栈,然后
1
1
1个元素进栈又出栈,则该矩阵变为
[
x
+
k
,
y
−
k
−
1
]
\left[ x+k,y-k-1 \right]
[x+k,y−k−1]其中
{
0
⩽
x
+
k
0
⩽
y
−
k
−
1
⩽
y
\left\{\begin{aligned}&0\leqslant x+k\\&0\leqslant y-k-1\leqslant y\end{aligned}\right.
{0⩽x+k0⩽y−k−1⩽y,则
max
(
−
x
,
−
1
)
⩽
k
<
y
,
k
∈
Z
\max(-x,-1)\leqslant k<y,k\in \mathbb{Z}
max(−x,−1)⩽k<y,k∈Z。
那么剩下的工作就很简单了,直接深搜+动规即可。
由于这条题目给的数据特别大,考虑使用大整数类(参考自刘汝佳《算法竞赛入门经典》124页)。
更优的方法是运用卡特兰数(Catalan number)。
卡特兰数指的是满足
h
(
n
)
=
{
1
,
n
=
1
∑
i
=
1
n
−
1
h
(
i
)
h
(
n
−
i
)
,
n
⩾
2
h(n)=\left\{\begin{aligned}&1&,n=1\\&\sum_{i=1}^{n-1} h(i)h(n-i)&,n\geqslant2\end{aligned}\right.
h(n)=⎩⎪⎪⎨⎪⎪⎧1i=1∑n−1h(i)h(n−i),n=1,n⩾2
的数
h
(
n
)
h(n)
h(n)。其递推关系的解为
h
(
n
)
=
1
n
+
1
C
2
n
n
h(n)=\frac{1}{n+1}C_{2n}^{n}
h(n)=n+11C2nn,推导过程可以百度。
卡特兰数的前几项为1,2,5,14,42,132,429,1430,4862,16796,……
卡特兰数的典型应用:
1.括号化问题,如矩阵链乘。
2.将多边形划分为三角形问题。
3.出栈次序问题,如本题。
那么,本题该如何应用卡特兰数呢?我们可以这样分析:
联系到上一题,如果设它让打印的in为
1
1
1,out为
0
0
0,那么,我们可以用一个
2
n
2n
2n位的二进制数来表示输出序列。比如,
123
123
123到
123
123
123的输出序列为
101010
101010
101010。
根据题意,如果对一个输出序列,在任意一个位置上数出从第一位到这一位的
1
1
1的数量和
0
0
0的数量,如果发现
1
1
1的数量小于
0
0
0的数量,则可以判定这个序列是不符合要求的。比如,
100011
100011
100011就是不符合要求的。
这里可能有人会有疑问,
123
123
123到
312
312
312的情况怎么判断它不符合要求呢?答案是显然不符合,因为它都不能表示成一个序列。
好了,接下来我们就可以开始推公式了,我们只要用所有的方法数减去不符合要求的方法数即可。
我们知道,恰含有
n
n
n个
1
1
1的
2
n
2n
2n位二进制数一共有
C
2
n
n
C_{2n}^n
C2nn个。而对于一个不符合要求的数,假设使它不符合要求的最小位数数为
k
k
k,即,在第
k
k
k位之前
0
0
0和
1
1
1的数量相等,而第
k
k
k位是
0
0
0。显然,
k
k
k是奇数,设
k
=
2
m
+
1
k=2m+1
k=2m+1,则第
k
k
k位以后共有
n
−
m
n-m
n−m个
1
1
1和
n
−
m
−
1
n-m-1
n−m−1个
0
0
0。对
k
+
1
k+1
k+1及其以后的部分取反,则对应一个
n
+
1
n+1
n+1个
0
0
0和
n
−
1
n-1
n−1个
1
1
1的二进制数。那么,对于任意一个恰含有
n
+
1
n+1
n+1个
0
0
0的二进制数,因为
0
0
0的总数比
1
1
1多,则一定存在一个位置,这个位置之前
0
0
0和
1
1
1一样多,而这个位置是
0
0
0。我们将这个位置之后的数取反即可得到一个不符合要求的二进制数。因此证得,这两者是一一对应的。那么,不符合要求的二进制数共有
C
2
n
n
+
1
C_{2n}^{n+1}
C2nn+1个。
因此所有符合要求的二进制数共有
C
2
n
n
−
C
2
n
n
+
1
=
(
2
n
)
!
n
!
n
!
−
(
2
n
)
!
(
n
+
1
)
!
(
n
−
1
)
!
=
(
2
n
)
!
n
!
n
!
−
n
n
+
1
(
2
n
)
!
n
!
n
!
=
1
n
+
1
C
2
n
n
=
h
(
n
)
\begin{aligned}& C_{2n}^n-C_{2n}^{n+1}\\=&\frac{(2n)!}{n!n!}-\frac{(2n)!}{(n+1)!(n-1)!}\\=&\frac{(2n)!}{n!n!}-\frac{n}{n+1}\frac{(2n)!}{n!n!}\\=&\frac{1}{n+1}C_{2n}^n\\=&h(n)\end{aligned}
====C2nn−C2nn+1n!n!(2n)!−(n+1)!(n−1)!(2n)!n!n!(2n)!−n+1nn!n!(2n)!n+11C2nnh(n)个。
因此,我们证明了该题就是让我们求前一百项卡特兰数。
下面上代码:
#include <bits/stdc++.h>
const int maxn=1e4;
int a[maxn];
using namespace std;
int main(){
int n,x,flag;
while(~scanf("%d",&n)){
if(n==1){
printf("1\n");
continue;
}
memset(a,0,sizeof(a));
a[maxn-1]=1;
for(int i=1;i<n;i++){
x=0;
for(int j=maxn-1;j>=0;j--){
a[j]=a[j]*(4*i+2)+x;
x=a[j]/10;
a[j]%=10;
}
x=0;
for(int k=0;k<maxn;k++){
a[k]=x*10+a[k];
x=a[k]%(i+2);
a[k]=a[k]/(i+2);
}
}
flag=0;
for(int i=0;i<maxn;i++){
if(a[i]) flag=1;
if(flag) printf("%d",a[i]);
}
printf("\n");
}
return 0;
}