[关闭]
@ysner 2018-10-31T06:28:40.000000Z 字数 1601 阅读 1608

[ZJOI2010]排列计数

DP 数论


题面

称一个的排列的,当且仅当时,
计算的排列中有多少是的,答案可能很大,只需输出模以后的值。

解析

看到题目中的条件,如果你手打过二叉堆模板,就会想起小根堆。
其实就是小根堆。

表示长度为的排列(小根堆)的答案。
考虑增量转移。
设小根堆根结点左子树大小为
那么要从剩下的个点中选个点组成左子树,有种方法。
左右两个子树是规模更小的子问题,递归下去就行了。

那么

现在问题是怎么快速求
在题目中,小根堆的构建方式是从上往下,一层层地从左往右排数。
那么先搞出大小为的堆排满的层数(为),剩下的几个数,随便算算即可。

为了节省预处理的时间,可以只预处理模数以内的阶乘,然后卢卡斯一下即可。

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstdlib>
  4. #include<cstring>
  5. #include<cmath>
  6. #include<algorithm>
  7. #define ll long long
  8. #define re register
  9. #define il inline
  10. #define fp(i,a,b) for(re int i=a;i<=b;++i)
  11. #define fq(i,a,b) for(re int i=a;i>=b;--i)
  12. using namespace std;
  13. const int N=1e6+100;
  14. int T,n,k,jc[N],inv[N],f[N],mod,lim,lg[N];
  15. il ll gi()
  16. {
  17. re ll x=0,t=1;
  18. re char ch=getchar();
  19. while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
  20. if(ch=='-') t=-1,ch=getchar();
  21. while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
  22. return x*t;
  23. }
  24. il ll ksm(re ll S,re ll n)
  25. {
  26. re ll T=S;S=1;
  27. while(n)
  28. {
  29. if(n&1) S=S*T%mod;
  30. T=T*T%mod;
  31. n>>=1;
  32. }
  33. return S;
  34. }
  35. il ll C(re ll x,re ll y)
  36. {
  37. if(x<0||y<0||x<y) return 0;
  38. if(x<mod&&y<mod) return 1ll*jc[x]*inv[y]%mod*inv[x-y]%mod;
  39. return C(x/mod,y/mod)*C(x%mod,y%mod)%mod;
  40. }
  41. il ll dfs(re int x)
  42. {
  43. if(~f[x]) return f[x];
  44. re int ls=x+1-(1<<lg[x+1]);
  45. if(ls<(1<<lg[x+1]-1)) ls+=(1<<lg[x+1]-1)-1;else ls=(1<<lg[x+1])-1;
  46. return f[x]=C(x-1,ls)*dfs(ls)%mod*dfs(x-1-ls)%mod;
  47. }
  48. int main()
  49. {
  50. memset(f,-1,sizeof(f));f[0]=f[1]=1;
  51. n=gi();mod=gi();
  52. lim=min(n,mod-1);
  53. jc[0]=1;fp(i,1,lim) jc[i]=1ll*jc[i-1]*i%mod;
  54. inv[lim]=ksm(jc[lim],mod-2);
  55. inv[0]=inv[1]=1;fq(i,lim-1,2) inv[i]=1ll*inv[i+1]*(i+1)%mod;
  56. fp(i,2,n+1) lg[i]=lg[i>>1]+1;
  57. printf("%lld\n",dfs(n));
  58. return 0;
  59. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注