HDU-1023 Train Problem II

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,yk1]其中 { 0 ⩽ x + k 0 ⩽ y − k − 1 ⩽ y \left\{\begin{aligned}&amp;0\leqslant x+k\\&amp;0\leqslant y-k-1\leqslant y\end{aligned}\right. {0x+k0yk1y,则 max ⁡ ( − x , − 1 ) ⩽ k &lt; y , k ∈ Z \max(-x,-1)\leqslant k&lt;y,k\in \mathbb{Z} max(x,1)k<y,kZ
那么剩下的工作就很简单了,直接深搜+动规即可。
由于这条题目给的数据特别大,考虑使用大整数类(参考自刘汝佳《算法竞赛入门经典》124页)。

更优的方法是运用卡特兰数(Catalan number)。
卡特兰数指的是满足 h ( n ) = { 1 , n = 1 ∑ i = 1 n − 1 h ( i ) h ( n − i ) , n ⩾ 2 h(n)=\left\{\begin{aligned}&amp;1&amp;,n=1\\&amp;\sum_{i=1}^{n-1} h(i)h(n-i)&amp;,n\geqslant2\end{aligned}\right. h(n)=1i=1n1h(i)h(ni),n=1,n2
的数 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 nm 1 1 1 n − m − 1 n-m-1 nm1 0 0 0。对 k + 1 k+1 k+1及其以后的部分取反,则对应一个 n + 1 n+1 n+1 0 0 0 n − 1 n-1 n1 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}&amp; C_{2n}^n-C_{2n}^{n+1}\\=&amp;\frac{(2n)!}{n!n!}-\frac{(2n)!}{(n+1)!(n-1)!}\\=&amp;\frac{(2n)!}{n!n!}-\frac{n}{n+1}\frac{(2n)!}{n!n!}\\=&amp;\frac{1}{n+1}C_{2n}^n\\=&amp;h(n)\end{aligned} ====C2nnC2nn+1n!n!(2n)!(n+1)!(n1)!(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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值