组合数学起步-排列计数[ZJOI2010][BZOJ2111]
<题面>
数据范围:$1 \leq N \leq 10^6, P \leq 10^9 $
这个题……
以为是排列,其实是组合
题目中说是从所有排列中找到Magic的,就是
$p_{i/2} \leq p_i$ 满足的情况
我拿着排列想了半天,发现
这个题和排列一点关系也没有
而且我打了几个表,看了下可行结果,也不能逆向求
后来是一个提示:用堆,dp
堆啊……
我扯了一会大根堆,发现堆的形态有不确定性,dp起来费力耗时
所以就是小根堆了:小根堆的形态不变,只要找填数的方案,
那么这里如何做呢?
对于每一个叶子节点和唯一值,只有一种方案
然后对于根节点,比根大的数作为叶节点,只要分成两部分就可以
但是如何分也不必要记录,只要记录方案数
由于是分步求解,
所以要把每个子树上的方法与本次分的方法相乘。
式子:$dp[i]=C_{siz[i]-1}^{siz[2*i]}*dp[i*2]*dp[i*2+1]$其中$siz[i]$是以$i$为根的树节点数
于是就可以得到结果$dp[1]$
当然要从$n$跑到$1$了
下面是另一部分
$C_n^m\%p$怎么求?
卢卡斯定理,p一定要是素数。
具体证明和代码见这里
蒟蒻不会了~~
然后里面的细节就是,求阶乘及其逆元,可以打表,现求逆元也可以。
用 $a^{p-2}$ 的快速幂求逆元
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #define N 2000100 5 #define LL long long 6 using namespace std; 7 LL p,n; 8 int siz[N]; 9 LL fac[N],inv[N],dp[N]; 10 LL ppow(LL a,LL b){ 11 LL k=1; 12 while(b){ 13 if(b&1)k=k*a%p; 14 a=a*a%p; 15 b>>=1; 16 } 17 return k; 18 } 19 void prerun(){ 20 for (int i=n;i>=1;i--){ 21 siz[i]=siz[i*2]+siz[i*2+1]+1; 22 } 23 fac[0]=1; 24 for (int i=1;i<=n;i++){ 25 fac[i]=fac[i-1]*i%p; 26 } 27 } 28 LL C(LL m,LL n){ 29 if(n<m)return 0; 30 return fac[n]*ppow(fac[m],p-2)%p*ppow(fac[n-m],p-2)%p; 31 } 32 LL lucas(LL m,LL n){ 33 if(m==0)return 1; 34 return lucas(m/p,n/p)*C(m%p,n%p)%p; 35 } 36 int main (){ 37 scanf("%lld%lld",&n,&p); 38 prerun(); 39 for (int i=n;i>=1;i--){ 40 dp[i]=lucas(siz[i*2],siz[i]-1); 41 if(i*2<=n)dp[i]=dp[i]*dp[i*2]%p; 42 if(i*2+1<=n)dp[i]=dp[i]*dp[i*2+1]%p; 43 } 44 printf("%lld\n",dp[1]); 45 return 0; 46 }
真不知道没有题解怎么活~~
主要参考:Rorschach_XR的[ZJOI2010]排列计数 题解
Miemeng真的蒻