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

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

https://www.luogu.org/problemnew/show/P2606#sub

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

画成二叉树后容易发现这就是一个小根堆。

于是就变成了求符合条件的小根堆数量。

显然根只能放当前最小数,然后给左子树分配左子树大小个数,右子树同理。

所以就有f[i]=C(i-1,l)*f[l]*f[r]。

另外这题卡快速幂的log,所以预处理。

#include<cstdio>
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e6+5;
inline int read(){
    int X=0,w=0;char ch=0;
    while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
    while(isdigit(ch))X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    return w?-X:X;
}
int lg[N],f[N],inv[N],fac[N];
int qpow(ll k,int n,int p){
    int ans=1;
    while(n){
    if(n&1)ans=(ll)ans*k%p;
    k=(ll)k*k%p;n>>=1;
    }
    return ans;
}
int C(int n,int m,int p){
    if(m>n)return 0;
    if(m==n)return 1;
    return (ll)fac[n]*inv[m]%p*inv[n-m]%p;
}
int lucas(int n,int m,int p){
    int ans=1;
    while(n&&m&&ans){
    ans=(ll)ans*C(n%p,m%p,p)%p;
    n/=p,m/=p;
    }
    return ans;
}
inline int lsize(int n){
    int c=lg[n]+1;
    if(c==1)return 0;
    int t=n-(1<<c-1)+1;
    return (1<<c-2)-1+min((1<<c-1>>1),t);
}    
int main(){
    int n=read(),p=read();
    
    lg[1]=0;fac[1]=1;
    for(int i=2;i<=n;i++){
    lg[i]=lg[i-1];
    if((1<<lg[i]+1)==i)lg[i]++;
    fac[i]=(ll)fac[i-1]*i%p;
    }
    
    int mx=min(p-1,n);
    inv[mx]=qpow(fac[mx],p-2,p);
    for(int i=mx-1;i>=0;i--)inv[i]=(ll)inv[i+1]*(i+1)%p;
    
    f[1]=f[2]=1;
    for(int i=3;i<=n;i++){
    int l=lsize(i);
    f[i]=(ll)lucas(i-1,l,p)*f[l]%p*f[i-l-1]%p;
    }
    printf("%d\n",f[n]);
    return 0;
}

+++++++++++++++++++++++++++++++++++++++++++

+本文作者:luyouqi233。               +

+欢迎访问我的博客:http://www.cnblogs.com/luyouqi233/ +

+++++++++++++++++++++++++++++++++++++++++++

posted @ 2018-04-29 11:26  luyouqi233  阅读(196)  评论(0编辑  收藏  举报