[BZOJ2111][ZJOI2010]Perm 排列计数(组合数学+lucas定理)

来源:互联网 发布:cc攻击防御 php 编辑:程序博客网 时间:2024/05/29 15:03

题目描述

传送门

题解

首先第一个位置肯定是1
若把i的两个儿子看做i*2和i*2+1,这样就形成了一颗以1为根的有根树
这棵树的形态是不变的,我们需要做的就是将1-n填到每一个节点里然后保证父亲小于儿子
对于一颗子树,我们考虑怎样选才能满足要求,可以发现由于数是1-n,两两数之间的相对大小是不变的,也就是说,不会出现这棵子树中可以填2,3,4而不能填3,4,5的情况
而假设我们选出了若干数填到这棵子树中,根一定是确定的,也就是这些数中最小的数
那么选数的方案只由子树的大小有关
可以写出递推式f(i)=c(size(i)-1,size(ls(i)))*f(i*2)*f(i*2+1),c是组合数
递归求解即可
不过还有一个问题就是,这里可能n>p导致np不互质,这样求组合数不能直接用逆元,要用lucas定理

代码

#include<algorithm>#include<iostream>#include<cstring>#include<cstdio>#include<cmath>using namespace std;#define LL long long#define N 1000005LL n,Mod,ans=1LL;LL size[N+1],mul[N+1];void calc(){    mul[0]=1LL;    for (LL i=1;i<=n;++i) mul[i]=mul[i-1]*i%Mod;}void exgcd(LL a,LL b,LL &x,LL &y){    if (!b) x=1LL,y=0LL;    else exgcd(b,a%b,y,x),y-=a/b*x;}LL inv(LL A,LL Mod){    LL a=A,b=Mod,x=0LL,y=0LL;    exgcd(a,b,x,y);    x=(x%b+b)%b;    if (!x) x+=b;    return x;}LL C(LL n,LL m){    if (m>n) return 0LL;    return mul[n]*inv(mul[m]*mul[n-m]%Mod,Mod)%Mod;}LL lucas(int n,int m,int Mod){    LL ans=1LL;    for (;m;n/=Mod,m/=Mod)        ans=ans*C(n%Mod,m%Mod)%Mod;    return ans;}void dfs(LL x){    LL l=0;    if (x*2<=n)    {        dfs(x*2);        size[x]+=size[x*2];        l=size[x*2];    }    if (x*2+1<=n)    {        dfs(x*2+1);        size[x]+=size[x*2+1];    }    ans=ans*lucas(size[x],l,Mod)%Mod;    ++size[x];}int main(){    scanf("%lld%lld",&n,&Mod);    calc();    dfs(1);    printf("%lld\n",ans);}

总结

①遇到求逆元一定要考虑ap是否互质

0 0
原创粉丝点击