luogu P1044 火车进出栈问题(Catalan数)

Catalan数就是魔法

火车进出栈问题即:

一个栈(无穷大)的进栈序列为 1,2,3,4,...,n 求有多少个不同的出栈序列?

将问题进行抽象, 假设'+'代表进栈, 则有'-'代表出栈

那么如果进栈序列为123, 则:

+ + + - - - 将1, 2, 3压入栈后再将3, 2, 1弹出 得到出栈序列为321

同样, + - + - + - 得到出栈序列为123

上面所述的均为合法进出栈的序列 可发现规律: 序列中 + 的个数等于 - 的个数

但是如 + - - + +  - 这样的序列, 在栈为空时仍进行弹出操作的, 为非法序列

进一步将 + - 序列抽象到平面直角坐标系中, + 代表向右走一格, -代表向上走一格, 可知y = x直线下方的序列均为合法序列, 如下图所示:

 

到这里, 火车进出栈问题就已经抽象为非降路径(只能向上走或向右走的路径)的问题

对于路径计数问题:

1)从(0,0)到(n,n)的非降路径等于n个x和n个y的排列数 即路径数为C(2n, n);

2)如果不是合法序列, 则会经过上图中的红线, 不合法序列一定会经过点(n -1, n), 从(0, 0)到(n -1, n)有C(2n, 2n-1)种不合法的路径;

3)综合1), 2) 并依据组合数性质化简

推导出公式:

 

我们可以得到出栈序列的总数为C(2n, n) / (n + 1).

下面以luogu P1044 栈为例, 完整代码如下:

/*
 * @Author: Hellcat
 * @Date: 2020-02-27 15:54:37
 */
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

ll C(int n, int m) {
    ll ans = 1;
    for(ll i = 1; i <= m; i++)
        ans = ans * (n - m + i) / i;
    return ans;
}

int main() {
    int n;
    scanf("%d", &n);
    printf("%lld\n", C(2*n, n) / (n + 1));
}

题给数据量不大 选择高效的组合数计算方法即可 无需高精度算法.

同时给出完全模拟的dfs暴力搜索算法如下:

dfs时候需要注意 为保证字典序输出优先考虑出栈.

#include <bits/stdc++.h>
using namespace std;

int n, cnt = 20;
vector<int> state1;
stack<int> state2;
int state3 = 1;

int res = 0;

void dfs() {
    if(!cnt) return;
    if(state1.size() == n) {
        res++;
        cnt--;
        for(auto x : state1) cout<<x;
        puts("");
        return;
    }
    // 按字典序输出 则出栈先
    if(state2.size()) { // 出栈
        state1.push_back(state2.top());
        state2.pop();
        dfs();
        state2.push(state1.back()); // 回溯
        state1.pop_back();
    }
    if(state3 <= n) { // 入栈
        state2.push(state3);
        state3++;
        dfs();
        state3--; // 回溯
        state2.pop();
    }
}

int main() {
    scanf("%d", &n);
    dfs();
    printf("%d\n", res);
}

输入

3

输出

123
132
213
231
321
5

posted @ 2020-02-27 17:59  hellcat9  阅读(302)  评论(0编辑  收藏  举报