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;
}

 

posted @ 2017-12-17 10:12  F.W.Nietzsche  阅读(224)  评论(0编辑  收藏  举报