组合数学起步-排列计数[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}$ 的快速幂求逆元

 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 }
View Code

真不知道没有题解怎么活~~

主要参考:Rorschach_XR的[ZJOI2010]排列计数 题解

posted @ 2019-07-04 10:06  Miemeng_麦蒙  阅读(400)  评论(0编辑  收藏  举报

小麦在雨中,蒙蒙的雾气

麦蒙不想有人骚扰他,如果有必要 联系 QQ:1755601414

如果你嫌广告大,那就喷我吧,不是博客园的锅。