[ZJOI2010] 排列计数 题解

洛谷题目传送门

看洛谷上讲的都不是太详细,我来水一篇

把题目换一下,\(\lfloor{i \over 2}\rfloor\) 变成 \(p_i < p_{2i},p_i < p_{2i+1}\)。

顺眼多了。

这不就是二叉树吗?!

那么题目可以转化为:

在一个有 \(n\) 个节点的(完全)二叉树上,把 \(1\thicksim n\) 填入,有多少种方案满足任意一个节点的值都小于其子节点的值。

考虑 DP ,看看转移方程怎么推:

\(siz_x\) 表示节点 \(x\) 的子树大小。

\(dp_x\) 表示节点 \(x\) 的子树的合法方案数。

先看一下 \(dp_1\) 是怎么来的。

根节点是全局最小值,所以填的数必须是 1。

在剩下有 \(n-1\) 个数,随便选 \(siz_2\) 个数,填到左子树里,剩下的数填右子树,共 \(C_{n-1}^{siz_2}\) 种方法。

选好填的数以后,左右子树的内部有 \(dp_2\) , \(dp_3\) 种方法。

根据乘法原理:

\[dp_1 = C_{n-1}^{siz_2}\ dp_2\ dp_3 \]

根据归纳法,可得转移方程为:

\[dp_x = C_{siz_{x-1}}^{siz_{2x}}\ dp_{2x}\ dp_{2x+1} \]

然后把叶子节点的dp值设为1,从下往上遍历即可,就做完了。

但是,这题还有个坑点,就是 \(mod\) 可能小于 \(n\) 。如果预处理阶乘逆元到 \(n\) ,就会导致求 \(C_n^m\) 时全是 0,答案也就错了。

所以,预处理时求到 \(mod-1\) ,\(C_n^m\) 用 \(Lucas\) 定理即可。

恭喜你 \(\color{red}{WA}\)+\(\color{red}{TLE}\)+\(\color{red}{RE}\) !

注意到 \(mod\leq 10^9,n\leq 10^6\),所以不能强行枚举到\(10^9\),当 \(mod > 10^6\) 时,那么一定大于 \(n\) ,所以,预处理的范围就取到 \(min(10^6,mod-1)\) 即可。

最后附上代码 \(QwQ\):

// Problem: P2606 [ZJOI2010] 排列计数
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2606
// Author: hemingxi_2030
// Memory Limit: 128 MB
// Time Limit: 1000 ms
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
#define int long long
#define qy(FILE) freopen(FILE".in","r",stdin);freopen(FILE".out","w",stdout);
using namespace std;
const int N=1e6+5,INF=0x3f3f3f3f3f3f3f3f;
int n,mod,dp[N],siz[N],MAX;
int power(int a,int b)
{
	int res=1;
	a=a%mod;
	while(b)
	{
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b/=2;
	}
	return res%mod;
}
int jie[N],jie_ni[N];
void init()
{
	jie[0]=1;
	for(int i=1;i<=MAX;i++)
		jie[i]=jie[i-1]*i%mod;
	jie_ni[MAX]=power(jie[MAX],mod-2);
	for(int i=MAX;i>0;i--)
		jie_ni[i-1]=jie_ni[i]*i%mod; 
}
int C(int x,int y)
{
	if(x<y||x<0||y<0) return 0;
	return jie[x]*jie_ni[y]%mod*jie_ni[x-y]%mod;
}
int Lucas(int x,int y)
{
	if(x<y) return 0;
	if(x<mod&&y<mod) return C(x,y);
	return C(x%mod,y%mod)*Lucas(x/mod,y/mod)%mod;
}
void dfs(int x)
{
	siz[x]=1;
	for(int y=2*x;y<=2*x+1&&y<=n;y++)
	{
		dfs(y);
		siz[x]+=siz[y];
	}
	if(x*2+1>n) dp[x]=1;
	else dp[x]=Lucas(siz[x]-1,siz[2*x])*dp[2*x]%mod*dp[2*x+1]%mod;
	// cout<<x<<' '<<dp[x]<<'\n';
}
signed main()
{
//	qy("q");
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>mod;
	MAX=min(1000000ll,mod-1);
	init();
	dfs(1);
	cout<<dp[1]<<'\n';
	return 0;
}
posted @ 2026-10-23 18:32  hemingxi_2030  阅读(17)  评论(0)    收藏  举报