●洛谷P2606 [ZJOI2010]排列计数

题链:

https://www.luogu.org/problemnew/show/P2606
题解:

组合数(DP),Lucas定理

 

首先应该容易看出,这个排列其实是一个小顶堆。
然后我们可以考虑dp:
令F[i]为小顶堆的i号节点那棵子树的方案数:
F[i]=F[i*2]*F[i*2+1]*C(size[i]-1,size[i*2])
含义就是左儿子的方案数*右儿子的方案数*当前i节点取走最小的那个值后分size[i*2]个数给左儿子的方案数。

(BZOJ上数据加强,可能会N>P,所以如果直接预处理阶乘和阶乘逆元可能会导致出现很多不该出现的0,所以这里考虑用Lucas定理)


代码:

 

#include<bits/stdc++.h>
#define MAXN 1000006
using namespace std;
int N,P,ANS=1;
int size[MAXN],fac[MAXN],inv[MAXN];
int fastpow(int a,int b){
	int ret=1; 
	if(a==0) return 1;
	for(;b;a=1ll*a*a%P,b>>=1)
		if(b&1) ret=1ll*ret*a%P;
	return ret;
}
void prepare(int m){
	fac[0]=inv[0]=1;
	for(int i=1;i<=m;i++) fac[i]=1ll*fac[i-1]*i%P;
	inv[m]=fastpow(fac[m],P-2);
	for(int i=m-1;i>=1;i--) inv[i]=1ll*inv[i+1]*(i+1)%P;
}
int C(int m,int n){
	int ret=1,nn,mm;
	while(m&&n){
		mm=m%P; nn=n%P; m/=P; m/=P;
		if(mm<nn) return 0;
		ret=1ll*ret*fac[mm]%P*inv[nn]%P*inv[mm-nn]%P;
	}
	return ret;
}
int main(){
	scanf("%d%d",&N,&P);
	prepare(min(N,P-1));
	for(int i=N;i>=1;i--) size[i]++,size[i/2]+=size[i];
	for(int i=1;i<=N;i++) if(i*2<=N) 
		ANS=1ll*ANS*C(size[i]-1,size[i*2])%P;
	printf("%d\n",ANS);
	return 0;
}

 

  

 

posted @ 2018-03-10 19:37  *ZJ  阅读(135)  评论(0编辑  收藏  举报