Perm排列计数(bzoj2111)

2111: [ZJOI2010]Perm 排列计数

Time Limit: 10 Sec   Memory Limit: 259 MB
Submit: 2686   Solved: 811
[ Submit][ Status][ Discuss]

Description

称一个1,2,…,N的排列P1,P2…,Pn是Magic的,当且仅当2<=i<=N时,Pi>Pi/2. 计算1,2,…N的排列中有多少是Magic的,答案可能很大,只能输出模P以后的值

Input

输入文件的第一行包含两个整数 n和p,含义如上所述。

Output

输出文件中仅包含一个整数,表示计算1,2,⋯, n的排列中, Magic排列的个数模 p的值。

Sample Input


20 23

Sample Output


16

HINT


100%的数据中,1 ≤ N ≤ 106, P≤ 10^9,p是一个质数。 数据有所加强

Source


[ Submit][ Status][ Discuss]

没错,我换版式了!
洛谷链接也给你们吧:排列计数

一开始,我很脑抽地领会错了题目的意思。
我以为那个P(i)>P(i/2)只对偶数管用,根本没想到i/2可以下去整
于是很快乐地做了一个小时
又是杨辉三角,又是log表,又是素数
搞得不亦乐乎
结果,当样例没过是,我才意识到……
What the f***?
于是,重头再来(有没有搞错,我的时间很宝贵诶)
算了,谁叫我不细心呢
于是我开始重新审视这道题
很容易联想到二叉树
因为对于一个节点i,它下面对应着两个节点i<<2和i<<2|1
所以,我们很自然地想到数位dp
所以我们设置两个数组size[]和f[]
f[i]表示以i为根节点的子树的种类数
显然最终答案是f[1]
size[i]是指以i为根节点的子树的节点数
那么转移方程就出来了:

f[i]=f[i<<1]*f[i<<1|1]*C(size[i]-1,size[i-1]);

这里的C(n,m)表示组合数
那么,接下来的重点,就是这个组合数
给的数据并不小诶
所以,我们就搬出了Lucas定理
具体知识点回头再说
代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
long long n,p;
long long f[2000005],size[2000005];
long long fac[2000005],inv[2000005];
void init(){
    fac[0]=fac[1]=1;
    inv[0]=inv[1]=1;
    for(int i=2;i<=n;i++){
        fac[i]=fac[i-1]*i%p;
//      inv[i]=(-p/i)*inv[p%i]%p;  
    }
    for(int i=2;i<=n;i++){
        inv[i]=(p/i+1)*inv[i-p%i]%p;
    }
    for(int i=2;i<=n;i++){
        inv[i]=inv[i]*inv[i-1]%p;
    } 
}
long long C(long long a,long long b){
    if(a<b){
        return 0;
    }
    if(a<p&&b<p){
        return fac[a]*inv[b]%p*inv[a-b]%p;
    }
    return C(a%p,b%p)*C(a/p,b/p)%p;
}
int main(){
    memset(size,0,sizeof(size));
    scanf("%lld%lld",&n,&p);
    init();
    for(int i=n;i>=1;i--){
        size[i]=size[i<<1]+size[i<<1|1]+1;
        f[i]=((i<<1)>n?1:f[i<<1])*((i<<1|1)>n?1:f[i<<1|1])%p*C(size[i]-1,size[i<<1])%p;
    }
    printf("%lld",f[1]);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/cggwz/article/details/81139763