bzoj 2111 [ZJOI2010]Perm 排列计数(DP+lucas定理)

时间:2023-03-09 19:51:31
bzoj 2111 [ZJOI2010]Perm 排列计数(DP+lucas定理)

【题目链接】

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

【题意】

给定n,问1..n的排列中有多少个可以构成小根堆。

【思路】

设f[i]为i个数的方案,设l为左子树大小r为右子树大小,则有:

f[i]=C(i-1,l)*f[l]*f[r]

因为是个堆,所以子树大小都是确定的,可以直接递推得到。

其中C(n,m) nm比较大,可以用lucas定理求。

模型建立的重要性可知一二。。。

【代码】

 #include<cstdio>
#include<iostream>
using namespace std; typedef long long ll;
const int N = 5e6+; int mod,n;
ll f[N],fac[N],s[N]; ll pow(ll a,ll p,int mod)
{
ll ans=;
while(p) {
if(p&) ans=(ans*a)%mod;
a=(a*a)%mod; p>>=;
}
return ans;
} void get_pre(int n)
{
fac[]=;
for(int i=;i<=n;i++)
fac[i]=(fac[i-]*i)%mod;
}
ll C(ll n,ll m,int mod)
{
if(n<m) return ;
if(n<mod&&m<mod) {
ll invn=pow(fac[n-m],mod-,mod);
ll invm=pow(fac[m],mod-,mod);
return fac[n]*invm%mod*invn%mod;
}
return C(n/mod,m/mod,mod)*C(n%mod,m%mod,mod)%mod;
} int main()
{
scanf("%d%d",&n,&mod);
get_pre(min(n,mod));
for(int i=n;i;i--) {
s[i]=s[i<<]+s[i<<|]+;
f[i]=C(s[i]-,s[i<<],mod);
if((i<<)<=n) f[i]=(f[i]*f[i<<])%mod;
if((i<<|)<=n) f[i]=(f[i]*f[i<<|])%mod;
}
printf("%lld\n",f[]);
return ;
}