P1044 [NOIP2003 普及组] 栈

[NOIP2003 普及组] 栈

题目描述

一个操作数序列,\(1,2,\ldots ,n\),栈 A 的深度大于 \(n\)

现在可以进行两种操作,

  1. 将一个数,从操作数序列的头端移到栈的头端(对应数据结构栈的 push 操作)
  2. 将一个数,从栈的头端移到输出序列的尾端(对应数据结构栈的 pop 操作)

使用这两种操作,由一个操作数序列就可以得到一系列的输出序列。
你的程序将对给定的 \(n\),计算并输出由操作数序列 \(1,2,\ldots,n\) 经过操作可能得到的输出序列的总数。

输入格式

输入文件只含一个整数 \(n\)\(1 \leq n \leq 18\))。

输出格式

输出文件只有一行,即可能输出序列的总数目。

样例 #1

样例输入 #1

3

样例输出 #1

5

分析

  • 法1:dfs枚举所有的排列,检查每个排列是否合法,TLE
  • 法2:dp思想,当前元素要么出栈,要么不出栈.
- 定义状态:f[i][j]  表示当前 i个入栈,j个出栈的情况数量
- 目标:f[n][n]
- 转移方程:
 f[i][0] = 1;
 f[i][j] = f[i-1][j] + f[i][j-1];
 当 i==j 时,f[i][j] 只能由 f[i][j-1] 出栈一个数得到
  • 法3:卡特兰数,利用卡特兰公式。

  • \(C_{i\in\{0,1,2...\}} : 1,1,2,5,14,42,132...\)

  • \(C_{i}\) 表示第 \(i\) 个卡特兰数, \(i\)\(0\) 开始。

  • \(C_n=\frac {C_{2n}^{n}}{n+1}\)

  • \(C_n= \frac {C_{n-1} \ (4n-2)} {n+1}\)

  • \(C_{n+1} = \sum_{k=0}^{n} C_{k}C_{n-k} = C_{0}C_{n}+C_{1}C_{n-1}+...+C_{n}C_{0}\)


  • 为何答案是卡特兰数的证明:假设 \(f_k\) 表示 \(k\) 最后出栈的方案数
  • 小于 \(k\) 的元素有 \(k-1\) 个,大于 \(k\) 的元素有 \(n-k\)
  • 所以 \(f_k=f_{k-1}f_{n-k}\)
  • 那么总方案数为 \(\sum_{k=1}^{n}f_{k}=\sum_{k=1}^{n}f_{k-1}f_{n-k}=f_0f_{n-1}+f_{1}f_{n-2}+...+f_{n-1}f_{0}=C_{n}\)
#include<iostream>
#include<stack>
using namespace std;
typedef long long LL;
const int N= 100;
LL n, ans=0,a[N], vis[N], id[N];

// 当入栈序列为1~n, 判断出栈序列arr[l,r]是否合法
// 对于一个合法序列,不可能出现当大数出栈后,大数前的小数出栈,再大数前面的大数出栈的情况
// 如:1 4 2 3 5 6:不合法于 4 2 3 --> 3比 2后入,则 3比 2先出
bool islegal(LL*arr, int l, int r) {
    for(int i=l; i<=r; i++) {
        for(int j=i+1; j<=r; j++) {
            if(arr[i]>arr[j]) {
                for(int k=j+1; k<=r; k++) {
                    if(arr[j]<arr[k] && arr[i]>arr[k]) {
                        return 0;
                    }
                }
            }
        }
    }
    return 1;
}
void print(LL*arr, int l, int r) {
    for(int i=l; i<=r; i++) cout<<arr[i]<<" \n"[i==n];
}
void dfs(int m) {
    if(m>n) {
        if(islegal(a,1,n)) ans++;
        return;
    }
    for(int i=1; i<=n; i++) {
        if(!vis[i]) {
            vis[i]=1, a[m]=i;
            dfs(m+1);
            vis[i]=0;
        }
    }
}

int f[N][N];
void slove2() { // dp思想
    // 当前元素要么出栈,要么不出栈
    // f[i][j]  表示当前 i个入栈,j个出栈的情况数量
    // f[i][0] = 1;
    // f[i][j] = f[i-1][j] + f[i][j-1];
    // 当 i==j时,f[i][j]只能由 f[i][j-1]出栈一个数得到
    for(int i=0; i<=n; i++) f[i][0]=1;
    for(int i=1; i<=n; i++) {
        for(int j=1; j<=i; j++) {
            if(i==j) f[i][j]=f[i][j-1];
            else f[i][j] = f[i-1][j]+f[i][j-1];
        }
    }
    ans=f[n][n];
}
void slove3() { // 卡特兰数的线性递推
    a[0]=1;
    for(int i=1; i<=n; i++) a[i]=a[i-1]*(4*i-2)/(i+1);
    ans=a[n];
}
int main() {
    cin>>n;
    // dfs(1);
    // slove2();
    slove3();
    cout<<ans<<endl;
    return 0;
}
posted @ 2022-10-18 13:12  HelloHeBin  阅读(258)  评论(0编辑  收藏  举报