1. 程式人生 > 其它 >P2606 [ZJOI2010]排列計數 分析

P2606 [ZJOI2010]排列計數 分析

分析

題意可以簡化為用 $[1,n]$ 的數,組成一個完全二叉樹,使其滿足小根堆性質,求方案數。

令 $f_i$ 表示在 $i$ 點的方案數,$s_i$ 表示 $i$ 的子節點個數(包括 $i$),於是得出遞推式:

$$
f_i=C^{s_i-1}_{s_{i \times 2}} \times f_{i*2} \times f_{i*2+1}
$$

由於 BZOJ 上 $n>m$(可能),所以需要用 Lucas 定理。

程式碼

namespace LZX
{
    using namespace std;
    #define int long long
    const int MAXN=2000015;
    int fac[MAXN],s[MAXN],f[MAXN];
    int math_qpow(int base,int power,int mod)
    {
        int res=1;
        while(power)
        {
            if(power&1)
            {
                res=res*base%mod;
            }
            base=base*base%mod;
            power>>=1;
        }
        return res;
    }
    int math_C(int n,int m,int p)
    {
        return fac[n]*math_qpow(fac[m]*fac[n-m]%p,p-2,p)%p;
    }
    int math_lucas(int n,int m,int p)
    {
        if(!m)
        {
            return 1;
        }
        if(m>n)
        {
            return 0;
        }
        return math_C(n%p,m%p,p)*math_lucas(n/p,m/p,p)%p;
    }
    int _main()
    {
        int n,m;
        scanf("%lld%lld",&n,&m);
        fac[0]=1;
        for(int i=1;i<=n;i++)
        {
            fac[i]=fac[i-1]*i%m;
        }
        fill(s+1,s+n+1,1);
        for(int i=n;i>=2;i--)
        {
            s[i>>1]+=s[i];
        }
        fill(f+n+1,f+n*2+2,1);
        for(int i=n;i;i--)
        {
            f[i]=math_lucas(s[i]-1,s[i<<1],m)%m*f[i<<1]%m*f[i*2+1]%m;
        }
        printf("%lld\n",f[1]);
        return 0;
    }
}