P2606 [ZJOI2010]排列计数(计数dp + Lucas定理)
P2606 [ZJOI2010]排列计数(计数dp + Lucas定理)
题目传送门
题目大意:
对于 \(1\) 到 \(n\) 的排列求出所有满足小根堆的方案数
题目分析:
-
[\(1\)]:首先我们知道了要找到小根堆的方案数,那么在根节点的数值一定是最小的。
-
[\(2\)]:我们先来考虑 \(dp\),那么如何设计状态呢,我们发现对于确定的长度 \(n\),我们可以确定他的形状,那么我们便可以确定他的方案数,所以我们令 \(f_i\) 为长度 \(i\) 的排列符合小根堆的情况。那么如何转移呢,对于 \(f_i\),我们是可以确定根节点的,那么只有 \(i - 1\) 个点是不确定的,而我们还可以确定左子树和右子树的大小,而对于左子树我们还有 \(C_{i - 1}^l\) 种选法,对于每种选法有 \(f_l\) 种符合的,随之也确定下来了右子树,那么可以得出 \(f_i = C_{i - 1}^l * f_l * f_r\)
代码实现:
- [\(1\)]:因为是给定的模数,难免会出现 \((n \% p)\) 时 \(p < n\) 的情况,所以我们需要用到 \(Lucas\) 定理来解决。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n , m;
const int M = 1e6 + 7;
int Pow(int a , int b) {
int ans = 1;
while(b) {
if(b & 1) ans = ans * a % m;
a = a * a % m;
b >>= 1;
}
return ans;
}
int fac[M] , f[M] , Log[M] , inv[M];
int C(int x, int y) {//Lucas 定理
if (!y) return 1;
int u = C(x / m, y / m), v = x % m, w = y % m, z;
if (v < w) z = 0;
else z = 1ll * (1ll * fac[v] * inv[w] % m) * inv[v - w] % m;
return 1ll * u * z % m;
}
signed main () {
ios::sync_with_stdio(0),cin.tie(0);
cin >> n >> m;
fac[0] = 1;
Log[0] = -1;//处理子树需要用到
for(int i = 1; i <= n; ++ i) fac[i] = fac[i - 1] * i % m, Log[i] = Log[i >> 1] + 1;;
for(int i = 0; i <= n; ++ i) inv[i] = Pow(fac[i] , m - 2);
f[1] = 1 , f[2] = 1 , f[3] = 2;
int l = 1 , r = 1;
for(int i = 4; i <= n; ++ i) {
if (i - (1 << Log[i]) + 1 <= (1 << Log[i] - 1)) l ++;
else r ++;
f[i] = C(i - 1 , l) * f[l] % m * f[r] % m;
}
cout << f[n];
}
[========]