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

[========]

本题完结!

posted @ 2022-09-06 11:01  L3067545513  阅读(32)  评论(0编辑  收藏  举报