P2606 [ZJOI2010] 排列计数 题解

题意:求有多少个排列满足:对于每一个 \(2 \le i \le n\)\(a_i > a_{\frac{i}{2}}\)

首先我们会发现这些数之间的大小关系形成了一个完全二叉树,因为每一个 \(a_i\) 都必须小于 \(a_{2\times i}\)\(a_{2 \times i + 1}\)

会发现本题的方案和每个位置具体的值并没有关系,而只在乎两个位置之间的大小关系。

那么我们设 \(dp_i\) 表示在大小为 \(i\) 的完全二叉树内放 \(i\) 个不同的数的方案数(注意这里只考虑两数之间的大小关系,而不考虑每个数具体的值)。根节点只能放最小的数,假设根节点的左子树有 \(L\) 个点,右子树有 \(R\) 个点。在剩下的 \(i-1\) 个数中,可以选 \(L\) 个放到左子树中,有 \(C_{i-1}^{L}\) 种选法,其余的放到右子树。所以有:

\[dp_i=C_{i-1}^{L} \times dp_L \times dp_{R} \]

因为 \(n,m\) 有可能大于 \(mod\),所以需要用 \(\text{Lucas}\) 定理来计算组合数,时间复杂度 \(O(n \log n)\)

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN = 1e6 + 10;
int n,mod,dp[MAXN],inv[MAXN],fac[MAXN],invfac[MAXN];
inline int C(int n,int m)
{
	if(n < m || m < 0) return 0;
	return fac[n] * invfac[m] % mod * invfac[n - m] % mod;
}
inline int Lucas(int n,int m)
{
	if(n < mod && m < mod) return C(n,m);
	return Lucas(n / mod,m / mod) * C(n % mod,m % mod) % mod;
}
signed main()
{
	cin >> n >> mod;
	inv[0] = fac[0] = invfac[0] = 1;
	inv[1] = fac[1] = invfac[1] = 1;
	for(int i = 2;i <= min(n,mod - 1);i++)
	{
		fac[i] = fac[i - 1] * i % mod;
		inv[i] = inv[mod % i] * (mod - mod / i) % mod;
		invfac[i] = invfac[i - 1] * inv[i] % mod;
	}
	dp[1] = dp[0] = 1;
	for(int i = 2;i <= n;i++)
	{
		int L = (1 << (__lg(i) - 1)) - 1,R = (1 << (__lg(i) - 1)) - 1;
		if((i - L - R - 1) <= (1 << (__lg(i) - 1))) L += (i - L - R - 1);
		else L += (1 << (__lg(i) - 1)),R = i - L - 1;
		dp[i] = Lucas(i - 1,L) * dp[L] % mod * dp[R] % mod;
 	}
	cout << dp[n];
 	return 0;
}
posted @ 2024-02-29 15:51  Creeper_l  阅读(6)  评论(0编辑  收藏  举报