进出栈序列问题(卡特兰数+组合数的质因数分解求法)

题意:给定 1   N 1~N 1 N N N N个整数和一个无限大的栈,每个数都要进栈并出栈一次。如果进栈的顺序为 1 , 2 , ⋯   , N 1,2,\cdots,N 1,2,,N,那么可能的出栈序列有多少种?
题解:先考虑搜索
c o d e : code: code:

#include<bits/stdc++.h>
using namespace std;
int n;
vector<int>state1;
stack<int>state2;
int state3=1,cnt=20;
void dfs()
{
    if(!cnt)return ;
    if(state1.size()==n){
        cnt--;
        for(auto &p:state1)cout<<p;
        cout<<endl;
        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()
{
    cin>>n;
    dfs();
    return 0;
}

但是数据范围如果是百万级别的话,搜索肯定是不行的,可以将出队序列转化成一个 ′ + − ′ &#x27;+-&#x27; +序列,进栈为 ′ + ′ &#x27;+&#x27; +,出栈为 ′ − ′ &#x27;-&#x27; ,只要能够求出所有合理的 ′ + − ′ &#x27;+-&#x27; +序列,之后通过将序列转化为图上的路径,所有序列最后都会走到 ( n , n ) (n,n) (n,n),不合理的序列通过适当转换都会走到 ( n − 1 , n + 1 ) (n-1,n+1) (n1,n+1),所以卡特兰数就是 C ( n , 2 ∗ n ) − C ( n − 1 , 2 ∗ n ) C(n,2*n)-C(n-1,2*n) C(n,2n)C(n1,2n),适当化简为 C ( n , 2 ∗ n ) N + 1 \frac{C(n,2*n)}{N+1} N+1C(n,2n),到这里就算结束了,但是这个组合数的算法是个大坑,只有使用质因数分解法算才不会导致被T,质因数分解法,比如看公式 C ( m , n ) = n ! m ! ( n − m ) ! C(m,n)=\frac{n!}{m!(n-m)!} C(m,n)=m!(nm)!n!,可以将上下都质因数分解,然后最后将剩余的质因数想乘就是答案了,这样比那种边乘边除节省了很多时间,边乘边除高精度最后会升的很大,这种数字只会依次递增而已。一个阶乘的质因数分解也是有公式的,比如 n ! n! n!中2的幂次,不是一位一位算的,公式是 n 2 + n 2 2 + n 2 3 + ⋯ \frac{n}{2}+\frac{n}{2^2}+\frac{n}{2^3}+\cdots 2n+22n+23n+,然后就能做出整个题目了。
c o d e : code: code:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=120000+5;
int n,primes[N],cnt,st[N],powers[N];
void get_primes(int n)
{
    for(int i=2;i<=n;i++){
        if(!st[i]){
            primes[cnt++]=i;
            for(int j=i+i;j<=n;j+=i){
                st[j]=1;
            }
        }
    }
}
int get(int n,int p)
{
    int s=0;
    while(n){
        s+=n/p;
        n/=p;
    }
    return s;
}
void multi(vector<ll> &a,int b)
{
    ll t=0;
    for(int i=0;i<a.size();i++){
        a[i]=a[i]*b+t;
        t=a[i]/100000000;
        a[i]=a[i]%100000000;
    }
    while(t){
        a.push_back(t%100000000);
        t/=100000000;
    }
}
void out(vector<ll> &a)
{
    printf("%lld",a.back());
    for(int i=a.size()-2;i>=0;i--)printf("%08lld",a[i]);
    printf("\n");
}
int main()
{
    int n;
    scanf("%d",&n);
    get_primes(n*2);
    for(int i=0;i<cnt;i++){
        int p=primes[i];
        powers[p]=get(2*n,p)-get(n,p)*2;
    }
    int k=n+1;
    for(int i=0;i<cnt&&primes[i]<=k;i++){
        while(k%primes[i]==0){
            k/=primes[i];
            powers[primes[i]]--;
        }
    }
    vector<ll>res;
    res.push_back(1);
    for(int i=0;i<cnt;i++){
        for(int j=0;j<powers[primes[i]];j++){
            multi(res,primes[i]);
        }
    }
    out(res);
    return 0;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值