[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 }