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

211 篇文章 0 订阅
35 篇文章 0 订阅

题目描述

传送门

题解

首先第一个位置肯定是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 1000005

LL 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是否互质

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值