bzoj2111ZJ2010排列计数_solution

Posted Der Barde, Nietzsche

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bzoj2111ZJ2010排列计数_solution相关的知识,希望对你有一定的参考价值。

          -by bzoj 

http://www.lydsy.com/JudgeOnline/problem.php?id=2111



考虑第i个位置上的数字的可能性只取决于第i/2位置上的数,以及剩余数集的大小,可以看出一个树形模型

考虑第i个位置上的数字只会影响第i*2与i*2+1两个位置的可能性,发现这是个二叉树(完全二叉树)

而且是类似小根堆的形式,于是这个树的形态固定,第1个位置上只能放1;

这启发我们进一步思考

对根(第1个位置)来说,他自己是数集中最小的那个,从剩下n-1个数字中,挑一些填满左子树的节点,剩下填右子树,由于只有数字的个数影响结果,所以对左右子树的填法可以看做一个类似的子问题递归进行;

于是有f[i]=C(sum[i<<1],sum[i<<1]+sum[i<<1|1])*f[i<<1]*f[i<<1|1];

C为组合数;

注意组合数的处理细节

O(nlogn);

代码:

#include<cstdio>
#define LL long long 
using namespace std;
int N;
LL P;
int sum[2000010];
LL inv[2000010];
LL Sqr(LL ,int );
void dfs(int );
LL dp(int );
LL C(int ,int );
int main()
{
    int i,j,k;
    scanf("%lld%lld",&N,&P);
    for(i=1;i<=N;i++)
        inv[i]=Sqr(i%P,P-2);
    dfs(1);
    printf("%lld",dp(1));
    return 0;
}
LL Sqr(LL x,int n){
    LL ret=1;
    while(n){
        if(n&1)
            (ret*=x)%=P;
        (x*=x)%=P,n>>=1;
    }
    return ret;
}
void dfs(int x){
    if((x<<1)<=N)
        dfs(x<<1);
    if((x<<1|1)<=N)
        dfs(x<<1|1);
    sum[x]=sum[x<<1]+sum[x<<1|1]+1;
}
LL dp(int x){
    LL a=1,b=1,ret=1;
    if((x<<1)<=N)
        a=dp(x<<1);
    if((x<<1|1)<=N)
        b=dp(x<<1|1);
    ret=a*b%P;(ret*=C(sum[x<<1],sum[x<<1]+sum[x<<1|1]))%=P;
    return ret;
}
LL C(int m,int n){
    int i,j=0;
    LL ret=1;
    for(i=1;i<=m;i++)
        if((n-i+1)%P!=0&&i%P!=0)
            (ret*=(((n-i+1ll)*inv[i])%P))%=P;
        else
            j+=((n-i+1)%P==0?1:0)+(i%P==0?-1:0);
    return  j>0?0ll:ret;
}

 

以上是关于bzoj2111ZJ2010排列计数_solution的主要内容,如果未能解决你的问题,请参考以下文章

BZOJ2111[ZJOI2010]Perm 排列计数 组合数

BZOJ2111:[ZJOI2010]排列计数——题解

bzoj2111: [ZJOI2010]Perm 排列计数

bzoj 2111 [ZJOI2010]Perm 排列计数(DP+lucas定理)

bzoj 2111: [ZJOI2010]Perm 排列计数 (dp+卢卡斯定理)

组合数学+lucas定理+逆元 BZOJ2111 [ZJOI2010]Perm 排列计数