题解 P2606 【[ZJOI2010]排列计数】

题目链接

Solution [ZJOI2010]排列计数

题目大意:求 (1-n) 的排列 (p) 中,有多少个排列满足 (forall iin [2,n],p_i > p_{lfloorfrac{i}{2} floor}),对给定质数 (m) 取模。


分析:

(p_i > p_{lfloorfrac{i}{2} floor}),反过来 (forall x,p_x<p_{2x},p_{2x+1}),那么问题变成一棵 (n) 个节点的完全二叉树,将数 (1-n) 分配给每一个节点,使得每个父节点都比它两个子节点权值要小的方案数。

进一步,我们不关心数究竟是啥,我们只关心它们之间的相对大小关系。

(siz[u]) 表示以 (u) 为根的子树大小,(f[u]) 表示将 (siz[u]) 个互不相同的数分配给以 (u) 为根的子树的合法方案数。记 (ls,rs) 为左右儿子。

那么 (f[u]=f[ls]cdot f[rs] cdot inom{siz[ls]+siz[rs]}{siz[ls]})

坑点在于,(n) 有可能比 (m) 大,此时需要使用 ( ext{Lucas}) 定理

#include <cstdio>
using namespace std;
const int maxn = 1e6 + 100;
int n,mod;
inline int add(const int a,const int b){return (a + b) % mod;}
inline int mul(const int a,const int b){return (1ll * a * b) % mod;}
inline int qpow(int base,int b){
	int res = 1;
	while(b){
		if(b & 1)res = mul(res,base);
		base = mul(base,base);
		b >>= 1;
	}
	return res;
}
inline int inv(const int x){return qpow(x,mod - 2);}
int fac[maxn];
inline void init(){
	fac[0] = 1;
	for(int i = 1;i <= n;i++)fac[i] = mul(fac[i - 1],i);
}
extern inline int C(const int n,const int m);
inline int lucas(const int n,const int m){
	if(n < mod)return C(n,m);
	return mul(C(n / mod,m / mod),C(n % mod,m % mod));
}
inline int C(const int n,const int m){return n < mod ? mul(fac[n],inv(mul(fac[m],fac[n - m]))) : lucas(n,m);}
int f[maxn << 2],siz[maxn << 2];
#define ls (u << 1)
#define rs (u << 1 | 1)
inline void dp(const int u){
	f[u] = 1;
	if(u < 1 || u > n)return;
	siz[u] = 1;
	dp(ls);
	dp(rs);
	siz[u] += siz[ls];
	siz[u] += siz[rs];
	f[u] = mul(
		C(siz[u] - 1,siz[ls]),
		mul(f[ls],f[rs])
	);
}
#undef ls
#undef rs
int main(){
	scanf("%d %d",&n,&mod);
	init();
	dp(1);
	printf("%d
",f[1]);
	return 0;
}
原文地址:https://www.cnblogs.com/colazcy/p/14133304.html