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;
}