Luogu P2606 [ZJOI2010]排列计数

[ZJOI2010]排列计数

题目描述

称一个 \(1 \sim n\) 的排列 \(p_1,p_2, \dots ,p_n\) 是 Magic 的,当且仅当

\[\forall i \in [2,n],p_i > p_{\lfloor i/2 \rfloor} \]

计算 \(1 \sim n\) 的排列中有多少是 Magic 的,答案可能很大,只能输出模 \(m\) 以后的值。

输入格式

一行两个整数 \(n,m\),含义如上所述。

输出格式

输出文件中仅包含一个整数,表示 \(1\sim n\) 的排列中, Magic 排列的个数模 \(m\) 的值。

样例 #1

样例输入 #1

20 23

样例输出 #1

16

提示

【数据范围】
对于 \(100\%\) 的数据,\(1\le n \le 10^6\), \(1\le m \le 10^9\)\(m\) 是一个质数。

用数组下标表示每一个点从而构成一个小根堆( \(i << 1\)\(i << 1 | 1\) 表示左右儿子)

从而易得第 \(i\) 个点的方案数为 \(f[father] = C^{size[lson]}_{size[i]-1}f[lson]f[rson]\)

其中\(size[i]\) 表示以 \(i\) 为根节点的子树大小
\(C^{size[lson]}_{size[i]-1}\) 表示从 \(size[i]-1\) (除了最小点做根节点)选 \(size[lson]\) 个点做左子树剩下的点做右子树



#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>

using namespace std;

int n, mod;
long long inv[5211314], f[5211314], dp[5211314];
int size[5211314];

long long GetC(long long a, long long b) {
	if (a < b) return 0;
	else return (f[a] * inv[a - b]) % mod * inv[b] % mod;
}

long long Lucas(long long a, long long b) {
	if (b == 0) return 1;
	else return Lucas(a / mod, b / mod) * GetC(a % mod, b % mod) % mod;
}

int main() {
	scanf("%d%d", &n, &mod);
	f[0] = f[1] = inv[0] = inv[1] = 1;
	for (int i = 2; i <= n; ++ i) {
		f[i] = f[i - 1] * i % mod;
		inv[i] = (mod - mod / i) * inv[mod % i] % mod;
	}
	for (int i = 2; i <= n; ++ i) {
		inv[i] = inv[i - 1] * inv[i] % mod;
	}
	dp[1] = 1;
	for (int i = n; i >= 1; -- i) {
		size[i] += 1;
		size[i >> 1] += size[i]; 
	}
	for (int i = n + 1; i <= 2 * n + 1; ++ i) dp[i] = 1;
	for (int i = n; i >= 1; -- i) {
		dp[i] = Lucas(size[i] - 1, size[i << 1]) % mod * (dp[i << 1] % mod * dp[i << 1 | 1] % mod) % mod;
	}
	cout << dp[1] % mod << endl;
	return 0;
}
posted @ 2023-06-14 10:24  觉清风  阅读(19)  评论(1编辑  收藏  举报