【BZOJ2111】[ZJOI2010]Perm 排列计数 组合数

【BZOJ2111】[ZJOI2010]Perm 排列计数

Description

称一个1,2,...,N的排列P1,P2...,Pn是Magic的,当且仅当2<=i<=N时,Pi>Pi/2. 计算1,2,...N的排列中有多少是Magic的,答案可能很大,只能输出模P以后的值

Input

输入文件的第一行包含两个整数 n和p,含义如上所述。

Output

输出文件中仅包含一个整数,表示计算1,2,⋯,的排列中, Magic排列的个数模 p的值。

Sample Input

20 23

Sample Output

16

HINT

100%的数据中,1 ≤ N ≤ 106, P ≤ 10^9,p是一个质数。

题解:题意可转化为:求n个节点能构成的完全二叉堆的个数。显然我们可以求出左右两棵子树的大小,然后分别递归下去即可。

细节有点多~

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
typedef long long ll;
const int maxn=1000010;
int m=1000000;
ll n,p;
ll jc[maxn],jcc[maxn],ine[maxn],f[maxn];
int Log[maxn];
ll C(ll a,ll b)
{
	if(a<b)	return 0;
	if(!b)	return 1;
	if(a<p&&b<p)	return jc[a]*jcc[b]%p*jcc[a-b]%p;
	return C(a%p,b%p)*C(a/p,b/p)%p;
}
ll calc(ll x)
{
	if(f[x])	return f[x];
	ll a=x-(1<<Log[x+1])+1;
	if(a<(1<<Log[x+1]-1))	a=(1<<Log[x+1]-1)-1+a;
	else	a=(1<<Log[x+1])-1;
	return f[x]=C(x-1,a)*calc(a)%p*calc(x-a-1)%p;
}
int main()
{
	scanf("%lld%lld",&n,&p);
	if(m>=p)	m=p-1;
	ll i;
	jc[0]=jcc[0]=1,ine[0]=ine[1]=1;
	for(i=2;i<=m;i++)	ine[i]=(p-(p/i)*ine[p%i]%p)%p;
	for(i=1;i<=m;i++)	jc[i]=jc[i-1]*i%p,jcc[i]=jcc[i-1]*ine[i]%p;
	for(i=2;i<=n+1;i++)	Log[i]=Log[i>>1]+1;
	f[0]=f[1]=1;
	printf("%lld",calc(n));
	return 0;
}
posted @ 2017-08-15 19:33  CQzhangyu  阅读(484)  评论(0编辑  收藏  举报