[luoguP2606] [ZJOI2010]排列计数(DP)

传送门

如果能够根据题意看出这是一个堆的话,那么就有些思路了。。

首先堆顶必须是最小元素,然后左右儿子可以预处理出来都有多少个数,

把剩余的数任意分配给两个儿子,用排列组合即可

dp(now) = dp(now << 1) * dp(now << 1 | 1) * C(sum[now] - 1, sum[now << 1])

#include <cstdio>
#define N 5000001
#define LL long long

int n;
LL p, inv[N], A[N], B[N], s[N];

inline LL C(int x, int y)
{
	return A[x] * B[y] % p * B[x - y] % p;
}

inline LL dp(int now)
{
	if(!s[now] || s[now] == 1) return 1;
	return dp(now << 1) * dp(now << 1 | 1) % p * C(s[now] - 1, s[now << 1]) % p;
}

int main()
{
	int i, j;
	scanf("%d %lld", &n, &p);
	inv[1] = A[1] = A[0] = B[0] = B[1] = 1;
	for(i = 2; i <= n; i++)
	{
		inv[i] = -(p / i) * inv[p % i] % p;
		A[i] = A[i - 1] * i % p;
		B[i] = B[i - 1] * inv[i] % p;
	}
	for(i = 1; i <= n; i++)
		for(j = i; j; j >>= 1) s[j]++;
	printf("%lld
", (dp(1) + p) % p);
	return 0;
}

  

原文地址:https://www.cnblogs.com/zhenghaotian/p/8202355.html