题目概述
题目链接:https://www.luogu.com.cn/problem/P2606。
称一个 \(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\) 以后的值。
对于 \(100\%\) 的数据,\(1\le n \le 10^6\), \(1\le m \le 10^9\),\(m\) 是一个质数。
分析1
这显然是一个小根堆,那么直接设 \(f_i\) 表示大小为 \(i\) 的排列(经典)满足小根堆性质的方案。
显然钦定第一个节点为 \(1\),然后就有:
\[f_{i}=\binom{i-1}{l}f_l\times f_r
\]
其中 \(l\) 是这个完全二叉树左子树的大小。
分析2
巧法:考虑将计数问题转化为概率问题。
我们可以求出满足这个条件的概率,最后在乘上总方案就行了,有时候概率题也可以用计数题的方法。
我们考虑一个大小为 \(k\) 的子树,那么显然我当前这个点满足小根堆的性质当且仅当为这里面的最小值,所以概率是 \(\frac{1}{k}\)。
所以 \(n\) 个点满足的概率为 \(\frac{1}{\prod_i sz_i}\),那么所有的答案为 \(\frac{n!}{\prod_i sz_i}\)。
代码
只有分析二的代码,分析一的我没有打(因为感觉分析二的方法更通用一点)。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stdlib.h>
#include <vector>
#define int long long
#define N 2000006
using namespace std;
int n,mod;
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
int sz[N],inv[N],jc[N];
int ans = 1,fz,fm;
int qpow(int a,int b) {int res = 1;while(b) {if (b & 1) res = res * a % mod;a = a * a % mod;b >>= 1;}return res;
}
void dfs(int cur) {sz[cur] = 1;if (ls(cur) <= n) dfs(ls(cur));if (rs(cur) <= n) dfs(rs(cur));sz[cur] += sz[ls(cur)] + sz[rs(cur)];int t = sz[cur];while(t % mod == 0) fm ++,t /= mod;ans = ans * inv[t % mod] % mod;
}
int f(int n,int m) {if (n / m <= 0) return 0;return n / m + f(n / m,m);
}
signed main(){cin >> n >> mod;jc[0] = jc[1] = inv[0] = inv[1] = 1;for (int i = 2;i <= n;i ++) jc[i] = jc[i - 1] * i % mod;for (int i = 2;i <= min(n,mod - 1);i ++) inv[i] = (mod - mod / i) * inv[mod % i] % mod;dfs(1);fz = f(n,mod);if (fz > fm) return cout << 0,0;else {for (int i = 1;i <= n;i ++) {int t = i;while (t % mod == 0) t /= mod;ans = ans * t % mod;}cout << ans;}return 0;
}
//7 3
