bzoj2111ZJ2010排列计数_solution
Posted Der Barde, Nietzsche
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bzoj2111ZJ2010排列计数_solution相关的知识,希望对你有一定的参考价值。
-by bzoj
http://www.lydsy.com/JudgeOnline/problem.php?id=2111
考虑第i个位置上的数字的可能性只取决于第i/2位置上的数,以及剩余数集的大小,可以看出一个树形模型
考虑第i个位置上的数字只会影响第i*2与i*2+1两个位置的可能性,发现这是个二叉树(完全二叉树)
而且是类似小根堆的形式,于是这个树的形态固定,第1个位置上只能放1;
这启发我们进一步思考
对根(第1个位置)来说,他自己是数集中最小的那个,从剩下n-1个数字中,挑一些填满左子树的节点,剩下填右子树,由于只有数字的个数影响结果,所以对左右子树的填法可以看做一个类似的子问题递归进行;
于是有f[i]=C(sum[i<<1],sum[i<<1]+sum[i<<1|1])*f[i<<1]*f[i<<1|1];
C为组合数;
注意组合数的处理细节
O(nlogn);
代码:
#include<cstdio> #define LL long long using namespace std; int N; LL P; int sum[2000010]; LL inv[2000010]; LL Sqr(LL ,int ); void dfs(int ); LL dp(int ); LL C(int ,int ); int main() { int i,j,k; scanf("%lld%lld",&N,&P); for(i=1;i<=N;i++) inv[i]=Sqr(i%P,P-2); dfs(1); printf("%lld",dp(1)); return 0; } LL Sqr(LL x,int n){ LL ret=1; while(n){ if(n&1) (ret*=x)%=P; (x*=x)%=P,n>>=1; } return ret; } void dfs(int x){ if((x<<1)<=N) dfs(x<<1); if((x<<1|1)<=N) dfs(x<<1|1); sum[x]=sum[x<<1]+sum[x<<1|1]+1; } LL dp(int x){ LL a=1,b=1,ret=1; if((x<<1)<=N) a=dp(x<<1); if((x<<1|1)<=N) b=dp(x<<1|1); ret=a*b%P;(ret*=C(sum[x<<1],sum[x<<1]+sum[x<<1|1]))%=P; return ret; } LL C(int m,int n){ int i,j=0; LL ret=1; for(i=1;i<=m;i++) if((n-i+1)%P!=0&&i%P!=0) (ret*=(((n-i+1ll)*inv[i])%P))%=P; else j+=((n-i+1)%P==0?1:0)+(i%P==0?-1:0); return j>0?0ll:ret; }
以上是关于bzoj2111ZJ2010排列计数_solution的主要内容,如果未能解决你的问题,请参考以下文章
BZOJ2111[ZJOI2010]Perm 排列计数 组合数
bzoj 2111 [ZJOI2010]Perm 排列计数(DP+lucas定理)