【HNOI2009】有趣的数列

容斥原理!!!

题目描述

我们称一个长度为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的值。

此题还是很简单,我们首先需要知道为什么是catalan数列。

 * A1<A3<A5<A7
 * A2<A4<A6<A8
 * 对于A4来说它一定大于前三个,对于A3来说它一定小于后五个
 * 所以可以推断奇数项 Ai<i.
 * 至于为什么是Catalan?
 * 其实就是从左往右扫每个数,把放在奇数看作入栈,偶数看作出栈;也可以用左(奇数)右(偶数)括号匹配来理解
 * {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}的括号匹配如下:
 * (=135,()()();(=134,()(());(=125,(())();(=124,(()());(=123,((()))

但是我们发现,这个p并不是一个素数,所以我们没法求逆元

但我们知道catalan公式:F(n)=C(2*n,n)/(n+1)=(2n!)/(n!(n+1)!)。

因为取模很麻烦,所以我们可以想到将每一个非素数的数,拆成几个素数之积,然后就可以计算出每一个素数被使用了多少次。进而进行快速幂计算答案.

#pragma GCC target("f16c")
#pragma GCC optimize("unroll-loops")
#pragma GCC target("sse3","sse2","sse")
#pragma GCC optimize(3,"Ofast","inline")
#pragma GCC optimize("no-stack-protector")
#pragma GCC target("avx","sse4","sse4.1","sse4.2","ssse3")
#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f
#define ll long long
#define eps 1e-5
const int N=2e6+8;
int n,p,m,ans;
int tot,prime[N];
bool visit[N];
inline void pri(int n) {
    for(int i=2;i<=n;i++) {
        if(!visit[i])
            prime[++tot]=i;
        for (int j=1,k;j<=tot&&(k=prime[j]*i)<=n;j++) {
            visit[k]=1;
            if (i%prime[j]==0)
                break;
        }
    }
}
inline int ksm(int a,int b) {
    int sum=1;
    while(b){
        if (b&1)
            sum=1LL*sum*a%p;
        a=1LL*a*a%p;
        b>>=1;
    }
    return sum;
}
int main(){ 
    scanf("%d%d",&n,&p);
    pri(m=n<<1);
    ans=1;
    for(int i=1;i<=tot;i++) {//计算出每一个素数被使用了多少次,后直接计算其快速幂. 
        int s=0;
        for(int j=m;j/=prime[i];s+=j)
            ;
        for(int j=n;j/=prime[i];s-=j)
            ;
        for(int j=n+1;j/=prime[i];s-=j)
            ;//容斥原理.求出每一个素数被计算了多少次,把不是素数的数拆成素数,后计算素数的快速幂.
        //此处后两个循环是减,因为公式是(2n!)/(n!(n+1)!),对于一个质数来说,他就需要出去他在n!与(n+1)!中被计算的次数. 
        if(s)
            ans=1LL*ans*ksm(prime[i],s)%p;
    }
    printf("%d",ans);
    return 0;
}
/* Catalan,因P不是质数,不能用逆元
 * A1<A3<A5<A7
 * A2<A4<A6<A8
 * 对于A4来说它一定大于前三个,对于A3来说它一定小于后五个
 * 所以可以推断奇数项 Ai<i.
 * F(n)=C(2*n,n)/(n+1)=(2n!)/(n!(n+1)!)
 * 分解质因数求即可,用动规解只能得50分
 * 至于为什么是Catalan?
 * 其实就是从左往右扫每个数,把放在奇数看作入栈,偶数看作出栈;也可以用左(奇数)右(偶数)括号匹配来理解
 * {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}的括号匹配如下:
 * (=135,()()();(=134,()(());(=125,(())();(=124,(()());(=123,((()))
 */

猜你喜欢

转载自www.cnblogs.com/Fast-Bird/p/11421799.html
今日推荐