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是一个质数。 数据有所加强
事实上题目少说一句i为奇数/2就向下取整(虽然说不说都可以推断,说了比较严谨)
既然这样我们可以把它想象成一个 二叉堆(小根堆) 满足magic 就是满足二叉堆 那么magic的方案数就是满足二叉堆的方案数。
但如果暴力计算方式会TLE,我们需要用一种别的方式计算
于是就有了树形dp 设f[i]为以i为根的方案数显然可以由两个儿子贡献过来
当前点因为根是最小的所以size[i]-1表示剩余的节点数 然后是在size[i]-1的范围内选size[left]数量的数分配给左儿子
于是
$f[i]=f[(i<<1)]*f[(i<<1|1)]*{C_{size[i]-1}^{size[i<<1]}}$
由于n比较大然后要用lucas定理
然后注意当前点不能大于n转移就完了
下面依然是本人丑陋的代码
1 #include<bits/stdc++.h> 2 #define ll long long 3 #define A 5100000 4 using namespace std; 5 ll p,n,m,size[A],f[A],biao[A],ma=0; 6 ll read() 7 { 8 ll f=1,x=0;char c=getchar(); 9 while(!isdigit(c)){if(c=='-') f=-1;c=getchar();} 10 while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();} 11 return f*x; 12 } 13 ll meng(ll x,ll k) 14 { 15 ll ans=1; 16 for(;k;k>>=1,x=x*x%p) 17 if(k&1) 18 ans=x*ans%p; 19 return ans; 20 } 21 ll a(ll n) 22 { 23 if(n<=5000000&&biao[n]) return biao[n]; 24 ll ans=1ll; 25 for(ll i=max((ma+1<=n?ma-1:1),1ll);i<=n;i++) 26 if(!biao[i]){ans=ans*i%p;if(i<=5000000)biao[i]=ans;} 27 else ans=biao[i]; 28 if(n<=5000000)ma=n; 29 return ans; 30 } 31 ll jicus(ll n,ll m) 32 { 33 if(m>n) return 0; 34 else return (a(n)*meng(a(m),p-2)%p*meng(a(n-m),p-2))%p; 35 } 36 ll lucas(ll n,ll m) 37 { 38 if(m==0) return 1; 39 return jicus(n%p,m%p)*lucas(n/p,m/p)%p; 40 } 41 void dfs(ll i) 42 { 43 f[i]=1; 44 size[i]=1; 45 if((i<<1)<=n) 46 { 47 dfs(i<<1); 48 size[i]+=size[i<<1]; 49 } 50 if(((i<<1)+1)<=n) 51 { 52 dfs((i<<1)+1); 53 size[i]+=size[i<<1|1]; 54 } 55 f[i]=f[(i<<1)<=n?(i<<1):0]%p*f[(i<<1|1)<=n?(i<<1|1):0]%p*lucas(size[i]-1,size[i<<1])%p; 56 } 57 int main() 58 { 59 f[0]=1; 60 n=read(),p=read(); 61 dfs(1); 62 cout<<f[1]<<endl; 63 }
当然也可以线性转移
for(int i=n;i;--i) { if(sz[i]<3) dp[i]=1; else dp[i]=1ll*dp[lch]*dp[rch]%mod*lucas(sz[i]-1,sz[lch])%mod; //不能给右儿子算组合数,可能越界 }
会快很多