[BZOJ2111]Perm排列计数

数论的坑深似海,组合数学就是个大海沟据教练所说这道题已经算简单的组合数学的题,然而我依旧是个废物

Perm 排列计数

 
内存限制:512 MiB 时间限制:1000 ms 标准输入输出
 
 
 
 
 
 
 
 

题目描述

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

输入格式

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

输出格式

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

样例

样例输入

20 23

样例输出

16

数据范围与提示

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

其实吧这道题仔细想想还真不太难,要求是$P_i>P_{\frac{i}{2}}$,他不仅包含偶数,还包含了奇数向下取整,那根据这个关系,我们其实可以构造出一个二叉树或者说是小根堆,如下图(WPS手工制作,有点丑)

这样的话设以点i为根的符合条件的树有f[i]个,那么

$f[i]=f[i{\times}2]{\times}f[i{\times}2+1]$

可是我们会发现树中的部分子树交换位置不会影响小根堆的成立,那么不影响的有多少种呢?

设以i为根的子树大小为size[i]

则共有$C_{size[i]+1}^{size[2{\times}i]}$

到此我们就可以知道

$f[i]=f[i{\times}2]{\times}f[i{\times}2+1]{\times}C_{size[i]+1}^{size[2{\times}2]}$

关于c的求解,由于n达到了$10^6$,所以需要用到Lucas定理,不会的可自行百度,不太难,Lucas+预处理逆元和阶乘就可以搞定c的求解

我求逆元的方法选择的是快速幂倒推,就会出现n>p时要特殊处理的情况,不然直接逆推所有的逆元都会变成0

还有就是注意一下long long我反正是死了

 1 #include<iostream>
 2 #include<cstdio>
 3 #define maxn 10001000
 4 #define ll long long
 5 using namespace std;
 6 ll ny[maxn],jc[maxn],size[maxn],f[maxn];
 7 ll n,p;
 8 ll ksm(ll a,ll b,ll c)
 9 {
10     ll ans=1;
11     a=a%c;
12     while(b>0)
13     {
14         if(b&1)  ans=(ans*a)%c;
15         b=b>>1;
16         a=(a*a)%c;
17     }
18     return ans%c;
19 }
20 ll c(ll x,ll y)//Lucas定理求c
21 {
22     if(x<y)  return 0;
23     if(x<p&&y<p)  return ((jc[x]*ny[x-y])%p*ny[y])%p;
24     return (c(x/p,y/p)*c(x%p,y%p))%p;
25 }
26 int main()
27 {
28     //freopen("shuju.in","r",stdin);
29     //freopen("WA.out","w",stdout);
30     scanf("%lld%lld",&n,&p);
31     jc[0]=1;  jc[1]=1;
32     for(int i=2;i<=n;++i)  jc[i]=(jc[i-1]*i)%p;//求阶乘
33     if(p<=n)//关于n>=p的特殊处理
34     {
35         //cout<<ksm(jc[p-1],p-2,p)<<endl;
36         ny[p-1]=ksm(jc[p-1],p-2,p);
37         for(int i=p-1;i>=1;--i)  ny[i-1]=(ny[i]*i)%p;
38     }
39     else
40     {
41         ny[n]=ksm(jc[n],p-2,p);
42         for(int i=n;i>=1;--i)  ny[i-1]=(ny[i]*i)%p;
43     }
44     /*for(int i=1;i<=n;++i)
45         cout<<i<<" "<<jc[i]<<" "<<ny[i]<<endl;*/
46     for(int i=n+1;i<=2*n+1;++i)  f[i]=1;//预处理出等于一的部分f
47     for(int i=n;i>=1;--i)
48     {
49         size[i]=size[i*2]+size[i*2+1]+1;
50         f[i]=((f[i*2]*f[i*2+1])%p*c(size[i]-1,size[i*2]))%p;
51         //cout<<"c="<<c(size[i]-1,size[i*2])<<endl;
52     }
53     printf("%lld\n",f[1]);
54     /*cout<<"f="<<endl;
55     for(int i=1;i<=n;++i)  cout<<f[i]<<" ";
56     cout<<endl;*/
57     return 0;
58 }

随手附赠我调代码时搞出来的随机数据生成,质数虽然不全,但是调出来基本够用了

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int zs[45]={2,3,5,7,11,13,17,19,23,29,31,37,12281,16381,21841,29123,38833,51787,69061,92083,122777,163729,218357,291143,388211,517619,690163,
 4 999983,1226959,1635947,2181271,2908361,3877817,5170427,6893911,9191891,2255871,16341163,21788233,29050993,38734667,51646229,68861641,91815541};
 5 int random(int x)
 6 {
 7     return rand()*rand()%x;
 8 }
 9 int main()
10 {
11     freopen("shuju.in","w",stdout);
12     srand(time(0));
13     int n=random(100)+1,p=random(45);
14     if(n<0)  n=-n;
15     if(p<0)  p=-p;
16     cout<<n<<" "<<zs[p]<<endl;
17     return 0;
18 }

 

posted @ 2019-07-02 14:11  hzoi_X&R  阅读(306)  评论(0编辑  收藏  举报