您的位置:首页 > 其它

BZOJ 2111 [ZJOI2010]Perm 排列计数

2016-09-28 11:35 369 查看
组合数+Lucas定理

题目可以转化成求1~n排列的小根堆数目,那么对于每一个i位置,他的子树节点个数是确定的,记为f[i],那么有f[i]=C(siz[i-1],siz[i<<1])*f[i<<1]*f[i<<1|1]

注意到n可能大于p,套Lucas定理即可

【Lucas定理】注意,当且仅当p是质数时才可以用Lucas定理。在不考虑求逆元的情况下,单次时间复杂度O(logpn)

#include<cstdio>
#define N 1000005
#define ll long long
using namespace std;
int n, p, siz[N<<1];
ll fac
, inv
, f[N<<1];
void init()
{
fac[0]=1;
for(int i = 1; i <= n && i < p; i++)
fac[i]=fac[i-1]*i%p;
inv[1]=1;
for(int i = 2; i <= n && i < p; i++)
inv[i]=-(p/i)*inv[p%i]%p;
inv[0]=1;
for(int i = 1; i <= n && i < p; i++)
inv[i]=inv[i]*inv[i-1]%p;

for(int i = n; i; i--)
siz[i]=siz[i<<1]+siz[i<<1|1]+1;

for(int i = n+1, ii = 2*N; i < ii; i++)
f[i]=1;
}
ll C(int m, int n)
{
if(m<n)return 0;
if(m<=p && n<=p)
{
return fac[m]*inv
%p*inv[m-n]%p;
}
else return C(m/p,n/p)*C(m%p,n%p)%p;
}
int main()
{
scanf("%d%d",&n,&p);
init();
for(int i = n; i; i--)
f[i]=f[i<<1]*f[i<<1|1]%p*C(siz[i]-1,siz[i<<1])%p;
printf("%lld\n",(f[1]+p)%p);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: