首页 > 技术文章 > [BZOJ2111] Perm 排列计数

casun547 原文

[组合数学T2]  [递推]


数学知识:

1.费马小定理:ap-1≡1(mod p)   (a任意整数,p质数) (详细讲解 https://www.cnblogs.com/zylAK/p/9569668.html

(简单证明:由欧拉定理 aφ(n)≡ 1(mod n) (正整数a,n互质) n为质数,φ(n)=n-1)

应用:由ap-1≡1(mod p)   a*ap-2≡1  即  inv[a]=ap-2(mod p)

 

2.lucas定理/组合数取模: 

https://www.cnblogs.com/fzl194/p/9095177.html)(https://blog.csdn.net/cutedumpling/article/details/81840047

lucas定理

lucas定理用于求C(n,m)mod p(p为质数)  的值

lucas(n,m,p)=C(n%p,m%p)*lucas(n/p,m/p,p)%p     递归出口lucas(n,0,p)返回1


 题目传送门

题目描述

称一个1,2,...,N的排列P1,P2...,Pn是Magic的,当且仅当2<=i<=N时,Pi>Pi/2. 计算1,2,...N的排列中有多少是Magic的,答案可能很大,只能输出模P以后的值

 

输入格式

输入文件的第一行包含两个整数 n和p,含义如上所述。

 

输出格式

输出文件中仅包含一个整数,表示计算1,2,?, ???的排列中, Magic排列的个数模 p的值。

 

样例

样例输入

20 23

样例输出

16

 

数据范围与提示

100%的数据中,1 ≤ ??? N ≤ 106, P??? ≤ 10^9,p是一个质数。 数据有所加强

 


题解  (题解参考 https://blog.csdn.net/yanzhenhuai/article/details/80615615

1.Pi>P(i/2)  对于i=4,5 ,P(i/2)都是2, 所以对于所有的数连边,可以看成一个小根堆。

2.定义   F[i] :以i为子树,含有majic的个数;

     显然最后输出结果 F[1];

3.定义size[i] :以i为子树的节点个数  倒着循环处理size

      size[i]=size[i<<1]+size[i<<1|1]+1;

4.F[i]的转移:  首先根据分步乘法原理,若左右子树互不影响,则节点i的方案数=左子树的方案数×右子树的方案数

    但由于对于下图

    

    若将4,5,6节点交换位置仍符合要求

    所以考虑组合数    对于1来说,右子树的组成可以是  从size[1]-1(不算1自己)个节点中的选出size[i<<1]个节点的情况数

            所以在原来结果上乘$C_{size[i]-1}^{size[i<<1]}$

            F·[i]=F[i<<1]*F[i<<1|1]*$C_{size[i]-1}^{size[i<<1]}$

            倒着循环进行递推即可

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cmath>
 5 #define ll long long
 6 using namespace std;
 7 
 8 const ll maxn=2000005;
 9 int n,mod;
10 ll size[maxn],f[maxn];
11 ll poww(ll a,ll b,ll p)
12 {
13     ll ans=1;
14     a%=p;
15     while(b)
16     {
17         if(b&1)  ans=ans*a%p;
18         b>>=1;
19         a=a*a%p;
20     }
21     ans%=p;
22     return ans;
23 }
24 ll inv(ll x)
25 {
26     return poww(x,mod-2,mod);
27 }
28 ll  C(ll x,ll y)
29 {
30     if(x<y) return 0;
31     if(y>x-y)  y=x-y;
32     ll up=1,down=1;
33     for(ll i=x-y+1;i<=x;i++)  up=up*i%mod;
34     for(ll i=1;i<=y;i++)   down=down*i%mod;
35     return up*inv(down)%mod;
36 }
37 ll lucas(ll x,ll y)
38 {
39     if(y==0)  return 1;
40     return C(x%mod,y%mod)*lucas(x/mod,y/mod)%mod;
41 }
42 
43 int main()
44 {
45     scanf("%d%d",&n,&mod);
46     for(ll i=n;i>=1;i--)
47         size[i]=size[i<<1]+size[i<<1|1]+1;
48     for(ll i=1;i<=maxn;i++)
49         f[i]=1;
50     for(ll i=n;i>=1;i--)
51         f[i]=(f[i<<1]*f[i<<1|1])%mod*lucas(size[i]-1,size[i<<1])%mod;
52     printf("%lld
",f[1]);
53 }
View Code
愿你在迷茫时,记起自己的珍贵。

推荐阅读