[ZJOI2010]排列计数

题目描述

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

输入输出格式

输入格式:

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

输出格式:

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

输入输出样例

输入样例#1:
20 23 
输出样例#1:
16

说明

100%的数据中,1 ≤N ≤ 10^6, P≤ 10^9,p是一个质数。

画图发现树的形状是唯一的

且对于一个子树的根,一定小于所有子树节点

也就是说,对于一个根节点,只要考虑给左右子树划分的方案
可以列出dp方程:

f[i]=f[2*i]*f[2*i+1]*C(size[2*i],size[2*i+1]+size[2*i])

这题据说n会大于p,也就是说1~n会含有p

那么就不能线性求逆元

统计出i!中p出现的次数num[i]和不算p的倍数的阶乘fac[i]

算组合数时,如果num[y]-num[x]-num[y-x]不为0直接返回0

逆元直接把fac[]带入拓展欧几里德,因为在算fac时排除了p,所以可行

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cstring>
 5 using namespace std;
 6 typedef long long lol;
 7 lol f[1000001],size[1000001],p,num[1000001],fac[1000001],n;
 8 lol exgcd(lol a,lol b,lol &x,lol &y)
 9 {
10   if (b==0) 
11     {
12       x=1;y=0;
13       return a;
14     }
15   lol d=exgcd(b,a%b,x,y);
16   lol t=x;x=y;y=t-(a/b)*y;
17   return d;
18 }
19 lol reverse(lol a)
20 {
21   lol x,y;
22   exgcd(a,p,x,y);
23   return (x%p+p)%p;
24 }
25 lol C(int x,int y)
26 {
27   lol ap=num[y],bp=num[x],cp=num[y-x];
28   if (ap-bp-cp) return 0;
29   lol s=(fac[y]*reverse(fac[x])%p)*reverse(fac[y-x])%p;
30   return s;
31 }
32 void dfs_dp(int x)
33 {
34   f[x]=1;
35   size[x]=1;
36   if (2*x<=n)
37     dfs_dp(2*x);
38   if (2*x+1<=n)
39     dfs_dp(2*x+1);
40   if (2*x<=n)
41   if (2*x+1>n||size[2*x+1]==0)
42     {
43       f[x]=f[2*x];
44       size[x]+=size[2*x];
45     }
46   else 
47     {
48       f[x]=((f[2*x]*f[2*x+1]%p)*C(size[2*x],size[2*x]+size[2*x+1])%p)%p;
49       size[x]+=size[2*x]+size[2*x+1];
50     }
51 }
52 int main()
53 {int i;
54   cin>>n>>p;
55   fac[0]=1;
56   for (i=1;i<=n;i++)
57     {
58       int x=i;
59       num[i]=num[i-1];
60        while (x%p==0)
61     {
62       num[i]++;
63       x/=p;
64     }
65        if (i%p==0) fac[i]=fac[i-1];
66        else fac[i]=fac[i-1]*i%p;
67     }
68   dfs_dp(1);
69   cout<<f[1];
70 }

 

posted @ 2017-10-09 20:24  Z-Y-Y-S  阅读(152)  评论(0编辑  收藏  举报