1. 程式人生 > 實用技巧 >題解 P2606 【[ZJOI2010]排列計數】

題解 P2606 【[ZJOI2010]排列計數】

題目連結

Solution [ZJOI2010]排列計數

題目大意:求 \(1-n\) 的排列 \(p\) 中,有多少個排列滿足 \(\forall i\in [2,n],p_i > p_{\lfloor\frac{i}{2}\rfloor}\),對給定質數 \(m\) 取模。


分析:

\(p_i > p_{\lfloor\frac{i}{2}\rfloor}\),反過來 \(\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 \binom{siz[ls]+siz[rs]}{siz[ls]}\)

坑點在於,\(n\) 有可能比 \(m\) 大,此時需要使用 \(\text{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\n",f[1]);
	return 0;
}