【一句话题意】最后希望你给出n个互不相同的数能构成多少个不同的大小为n的二叉堆(大根堆或小根堆都算二叉堆,不同定义为至少有一个位置上数不同)。
n<=5e6
【分析】对于二叉堆,顶端最小(或最大)是确定的,剩余元素的分配其实是随意的。将剩余的元素随意分配到左右两棵子树,离散化之后就变成了之前的一个已求得的子问题。可以得到一个(伪)状态转移方程
f
[
x
]
=
f
[
x
左
子
树
节
点
数
]
∗
f
[
x
右
子
树
节
点
数
]
∗
C
x
−
1
左
(
右
)
子
树
节
点
数
f[x]=f[x左子树节点数]*f[x右子树节点数]*C^{左(右)子树节点数}_{x-1}
f[x]=f[x左子树节点数]∗f[x右子树节点数]∗Cx−1左(右)子树节点数
关键在于组合数的快速处理,可以用一个前缀积维护阶乘,那么一次递推的复杂度就是求一次逆元的复杂度(logn)。
然而毒瘤的出题人还是把这种算法给卡掉了。听说因为递推到n会有一定的无用状态,所以乱搞+记忆化搜索可以卡过(雾)
其实可以先求n的逆元在O(n)逆着乘回去,就可以快速得到逆元了。(强啊,yzf)
【code】
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=5e6+1000;
const LL mod=1e9+7;
int n,ans=0;
int lt[maxn],rt[maxn];
LL f[maxn],mul[maxn],inv[maxn];
LL pw(register LL x,register LL p){
register LL ret=1;
while(p>0){
if(p&1) ret=ret*x%mod;
x=x*x%mod,p>>=1;
}
return ret;
}
inline LL C(register LL a,register LL b){
return mul[b]*inv[b-a]%mod*inv[a]%mod;
}
int main(){
cin>>n;
f[1]=1,f[2]=1;
register int l=1,r=1,fl=0;
for(register int i=3;i<=n;i++){
lt[i]=l,rt[i]=r;
if(fl){
r++;if(r==l) fl=0;
}
else{
l++;if(l>(r<<1)) fl=1;
}
}
mul[0]=1;
for(register int i=1;i<=n;i++)
mul[i]=mul[i-1]*i%mod;
inv[n]=pw(mul[n],mod-2);
for(register int i=n-1;i>=1;i--)
inv[i]=inv[i+1]*(i+1)%mod;
for(register int i=3;i<=n;i++)
f[i]=f[lt[i]]*f[rt[i]]%mod*C(rt[i],i-1)%mod;
ans=(f[n]<<1)%mod;
if(n==1) cout<<1<<endl;
else cout<<ans<<endl;
return 0;
}