bzoj 1485 卡特兰数



链接:戳这里


1485: [HNOI2009]有趣的数列


Time Limit: 10 Sec  Memory Limit: 64 MB
[Submit][Status][Discuss]
Description

 我们称一个长度为2n的数列是有趣的,当且仅当该数列满足以下三个条件:

    (1)它是从1到2n共2n个整数的一个排列{ai};

    (2)所有的奇数项满足a1<a3<…<a2n-1,所有的偶数项满足a2<a4<…<a2n;

    (3)任意相邻的两项a2i-1与a2i(1≤i≤n)满足奇数项小于偶数项,即:a2i-1<a2i。

    现在的任务是:对于给定的n,请求出有多少个不同的长度为2n的有趣的数列。因为最后的答案可能很大,所以只要求输出答案 mod P的值。

Input

输入文件只包含用空格隔开的两个整数n和P。输入数据保证,50%的数据满足n≤1000,100%的数据满足n≤1000000且P≤1000000000。

Output

仅含一个整数,表示不同的长度为2n的有趣的数列个数mod P的值。

Sample Input

    3 10                  
Sample Output

  5


    对应的5个有趣的数列分别为(1,2,3,4,5,6),(1,2,3,5,4,6),(1,3,2,4,5,6),(1,3,2,5,4,6),(1,4,2,5,3,6)。


题意:额 中文题的话就不需要多说了吧


思路:一开始肯定是懵逼了,毕竟我连基本的dp状态都推不出来,然后因为是已经知道了是卡特兰数了,所以在证明为什么是。首先要求奇数位置升序,偶数位置升序,相邻的奇数偶数项满足a[i*2-1]<a[i*2]。然后我们抽象出一个模型:有2n个人排成一行进入剧场。入场费5元。其中只有n个人有一张5元钞票,另外n人只有10元钞票,剧院无其它钞票,问有多少中方法使得只要有10元的人买票,售票处就有5元的钞票找零?(将持5元者到达视作将5元入栈,持10元者到达视作使栈中某5元出栈)  这个是卡特兰数的经典例子

我们也这样设想一下,1~2*n顺序去填序列,因为放的奇偶位置不一定要相匹配,假设当前奇数位置上填了p个,偶数位置上已经填了q个,我们是不是只要满足p<=q就可以了呢?想一下我们是按顺序去填的序列,但是因为不一定要你一个我一个的去填,而是满足p<=q就可以了,类似于了有一个偶数位置进栈,就有一个奇数位置出栈,类似于那种n*n推导的dp方程式,f(n)=f(0)f(n-1)+f(1)f(n-2)+……+f(n-1)f(0) 可以求出答案,但是这道题n=1e6,所以还是回到C(2*n,n)/(n+1)吧

好了我们知道了答案之后还是不能求出正确的答案,因为要取模,而mod是给定的,不一定是质数,排列组合中有除法,总所周知除法取模会出错。那么我们可以简单化简一下C(2*n,n)/(n+1)==(2*n)*(2*n-1)*...*(n+2)*(n^-1)*((n-1)^-1)*...*(1^-1) 嗯这样是不是好看了一点,可是并没有什么卵用

接下来我们分析一个地方,就是怎么简化这个式子,因为每个数都可以变成一些质数的乘积,我们把(2*n)*(2*n-1)*...*(n+2)*(n^-1)*((n-1)^-1)*...*(1^-1)这些数全部都换成质数相乘,也就是一些质数的num次方的总乘积,质数取模的话应该是ok的,所以要模拟O(n*2)的筛选过程了,具体看代码吧



代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<vector>
#include <ctime>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<iomanip>
#include<cmath>
#define mst(ss,b) memset((ss),(b),sizeof(ss))
#define maxn 0x3f3f3f3f
#define MAX 1000100
///#pragma comment(linker, "/STACK:102400000,102400000")
typedef long long ll;
typedef unsigned long long ull;
#define INF (1ll<<60)-1
using namespace std;
int P,n;
int vis[2000100],prime[2000100],m,a[2000100],num[2000100];
void init(){
    m=0;
    for(int i=2;i<=n*2;i++){
        if(!vis[i]) {
            prime[++m]=i;
            a[i]=i;
        }
        for(int j=1;j<=m;j++){
            if(i*prime[j]>2*n) break;
            vis[i*prime[j]]=1;
            a[i*prime[j]]=prime[j];
            if(i%prime[j]==0) break;
        }
    }
}
ll qpow(int a,int b,int mod){
    ll ans=1;
    while(b){
        if(b%2==1) ans=ans*a%mod;
        b/=2;
        a=a*a%mod;
    }
    return ans;
}
int main(){
    scanf("%d%d",&n,&P);
    init();
    for(int i=1;i<=n;i++) num[i]--;
    for(int i=n+2;i<=n*2;i++) num[i]++;
    for(int i=n*2;i>=2;i--){
        if(a[i]!=i){
            num[i/a[i]]+=num[i];
            num[a[i]]+=num[i];
            num[i]=0;
        }
    }
    ll ans=1;
    for(int i=n*2;i>=2;i--){
        ans=ans*qpow(i,num[i],P)%P;
    }
    printf("%lld\n",ans);
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值