1. 程式人生 > >【BZOJ2111】[ZJOI2010]Perm 排列計數 組合數

【BZOJ2111】[ZJOI2010]Perm 排列計數 組合數

文件的 二叉堆 合數 name string stream main 文件 ...

【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;
}

【BZOJ2111】[ZJOI2010]Perm 排列計數 組合數