这东西还是挺有思想的,道听途说一些东西,问问DuanYue同志,然后自己打表画树推了推,就搞出来了。
首先根据p i>p i/2(向下取整)这种形式,如果线段树学的好的人,一定能看出来,这是在唯一标号法标号后的形式,即父亲的权值大于两个儿子的权值,这是一个小根堆的样子。
那问题就是求给定数目的数字,求其能构成小根堆的个数。
这是一个类似树形dp的问题,我们设f[i]表示以当前节点为根所能构成小根堆的个数,那么有状态转移方程:
就是说当前节点及其子树一共分配了size[i]个数字,然后分给了左右子树,有上式Combine种可能,然后把左右儿子的贡献转移上来。
组合数还是老规矩打阶乘及其逆元的表,用lucas定理求一下,因为不用的话模数小的时候会崩,模数大了lucas也可以自动求组合取模。
实现过程中没有定义dp数组(其实size数组也没必要),直接带返回值的dfs搞一下。
代码略丑,勿看(主要是懒,直接define int long long 了)。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<algorithm> #include<cmath> #include<cstring> #include<cstdio> #include<vector> #include<queue> #define int long long using namespace std; int n,p; int size[2000000]; int fac[2000000],inv[2000000]; int qpow(int x,int k){ int ans=1; for(;k;k>>=1,x=1ll*x*x%p) if(k&1) ans=1ll*ans*x%p; return ans; } void pre(){ fac[0]=1; for(int i=1;i<min(n,p);i++) fac[i]=1ll*fac[i-1]*i%p; inv[min(n,p)-1]=qpow(fac[min(n,p)-1],p-2); for(int i=min(n,p)-1;i>=1;i--) inv[i-1]=1ll*inv[i]*i%p; } int com(int x,int y){ if(y>x) return 0; return ((1ll*fac[x]*inv[y])%p*inv[x-y]%p)%p; } int lucas(int x,int y){ if(!y) return 1; return com(x%p,y%p)*lucas(x/p,y/p)%p; } int dfs(int x){ if(x>n) return 1; size[x]++; int a=dfs(x<<1)%p; int b=dfs(x<<1|1)%p; size[x]+=size[x<<1]+size[x<<1|1]; return 1ll*((a*b)%p*(lucas(size[x]-1,size[x<<1])%p))%p; } signed main(){ scanf("%lld%lld",&n,&p); pre(); /*for(int i=1;i<=p;i++) cout<<fac[i]<<" ";cout<<endl; for(int i=1;i<=p;i++) cout<<inv[i]<<" ";cout<<endl;*/ printf("%lld",dfs(1)%p); return 0; }