[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,从下往上遍历即可,就做完了。
但是,这题还有个坑点,就是 \(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;
}

浙公网安备 33010602011771号