BZOJ2111: [ZJOI2010]Perm 排列计数

BZOJ2111

根据题目所给信息,要求所有 2<=i<=N 时,满足 Pi>Pi/2
列一下,就是: p2>p1 , p3>p1 , p4>p2 , p5>p2 , p6>p3 , p7>p3
发现很像一颗二叉树!就是一颗二叉树。。
满足根节点小于两个儿子节点。
然后显然有子结构,可以 dp
对于一颗以 i 为根的子树,令l=i<<1,r=i<<1|1, sz[i] 表示子树大小。
f[i]=f[l]f[r]Csz[l]sz[i]1
i 为子树共有sz[i]个取值,显然最小的要放在 i 上,那么将剩下的sz[i]1 sz[l] 放在左子树上,剩下的放在右子树上。然后就可以愉快的 dp 了。
然而 p 可能大于n,所以要用 Lucas 定理。
预处理只用预处理到 min(n,p1) 即可。因为显然这之后的用不到啊。。
当然也可以不用预处理逆元每次直接费马小定理算,但是阶乘是需要预处理的。

【代码】

线性预处理逆元 424ms

#include <cstdio>
#include <iostream>
#include <algorithm>
#define N 1000001
#define INF 0x7fffffff
using namespace std;
typedef long long ll;
typedef pair<int,int> pa;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return x*f;
}

int n,mod;
int f[N],Fac[N],sz[N],Inv[N];

int Lucas(int x,int y)
{
    if(x<y) return 0;
    if(x<mod&&y<mod) return 1LL*Fac[x]*Inv[y]%mod*Inv[x-y]%mod;
    return 1LL*Lucas(x/mod,y/mod)*Lucas(x%mod,y%mod)%mod;
}

int main()
{
    n=read(),mod=read();Fac[0]=1;f[n+1]=1;Inv[1]=Inv[0]=1;int mx=min(n+1,mod);
    for(int i=1;i<mx;i++) Fac[i]=1LL*Fac[i-1]*i%mod;
    for(int i=2;i<mx;i++) Inv[i]=1LL*(mod-mod/i)*Inv[mod%i]%mod;
    for(int i=1;i<mx;i++) Inv[i]=1LL*Inv[i]*Inv[i-1]%mod;
    for(int i=n;i;i--)
    {
        static int l,r;l=i<<1,r=i<<1|1;
        l=min(l,n+1),r=min(r,n+1);
        sz[i]=sz[l]+sz[r]+1;
        f[i]=1LL*f[l]*f[r]%mod*Lucas(sz[i]-1,sz[l])%mod;
    }
    printf("%d\n",f[1]);
    return 0;
}

不预处理直接计算逆元 2048ms

#include <cstdio>
#include <iostream>
#include <algorithm>
#define N 1000001
#define INF 0x7fffffff
using namespace std;
typedef long long ll;
typedef pair<int,int> pa;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return x*f;
}

int n,mod;
int f[N],Fac[N],sz[N];

int Qpow(int x,int y)
{
    int rtn=1;
    while(y){
        if(y&1) rtn=1LL*x*rtn%mod;
        x=1LL*x*x%mod;y>>=1;
    }
    return rtn;
}

int Lucas(int x,int y)
{
    if(x<y) return 0;
    if(x<mod&&y<mod) return 1LL*Fac[x]*Qpow(1LL*Fac[y]*Fac[x-y]%mod,mod-2)%mod;
    return 1LL*Lucas(x/mod,y/mod)*Lucas(x%mod,y%mod)%mod;
}

int main()
{
    n=read(),mod=read();Fac[0]=1;f[n+1]=1;
    for(int i=1;i<N;i++) Fac[i]=1LL*Fac[i-1]*i%mod;
    for(int i=n;i;i--)
    {
        static int l,r;l=i<<1,r=i<<1|1;
        l=min(l,n+1),r=min(r,n+1);
        sz[i]=sz[l]+sz[r]+1;
        f[i]=1LL*f[l]*f[r]%mod*Lucas(sz[i]-1,sz[l])%mod;
    }
    printf("%d\n",f[1]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值