BZOJ 2111 浅谈完全二叉树组合数递推半未来状态设计转移

这里写图片描述
世界真的很大
略略的有点难懂的题目,想出来了还是比较简单
完全二叉树的性质,很大一部分上决定了这道题的递推方法
组合数能干嘛?还是有了一点点更加清楚的认知吧

看题先:

description:

称一个1,2,…,N的排列P1,P2…,Pn是Magic的,当且仅当2<=i<=N时,Pi>Pi/2. 计算1,2,…N的排列中有多少是Magic的,答案可能很大,只能输出模P以后的值

input:

输入文件的第一行包含两个整数 n和p,含义如上所述。

output:

输出文件中仅包含一个整数,表示计算1,2,⋯, �的排列中, Magic排列的个数模 p的值。

题目大概是把一个排列扔进一个完全二叉树,使得其是一个小根堆的方案数
一开始的却是无从下手
考虑手写堆的时候,如果给定了一个堆的大小,那么堆的形态岂不是完全确定了
1~n的排列,换句话说同时也给定了堆的大小和结构
对于一个特定大小的堆,往里面填充排列的方案数是固定的(这是废话,不然题目求什么?)
一个堆的部分也是一个堆。
这样让我们直接可以看出来具有部分转移的性质的模型,很容易让人想到递推,或者说DP
设f[i]表示以i为根的子树构成的堆的方案数。

但这样有一个小小的问题。
我们在计算i的时候,甚至连i的子树里面有哪些数都不知道,又如何去统计方案呢?
基于未来状态说的就是这样一回事
假定在计算i的时候,已经知道了在i的子树里有哪些数存在以进行答案的计算
因为反正i的子树里面的数的总数是确定的,而且每一个数都是互不相同的,参考hash,每一个数究竟是什么根本无关紧要,我们想要知道的,只是当有这么多互不相同的数的时候,方案数是多少
比如说小根堆,堆顶肯定是1,假设左子树有10个,右子树有11个,给左子树哪10个的方案数不应该是相等的吗?
换句话说,我们在进行每个子树的计算的时候,都是假设这个子树里面填充的是1~(子树大小)的排列。
直观一点,反正hash完了之后就是这些值
那么我们在合并子树的时候只需要考虑是把哪些元素分给那个子树了,这就是组合数了
因为我们并不关心在i的时候究竟有哪些数,我们只需要知道这些书是互不相同而且知道他们的数量就行了
然后得到方程:
f(i) = C(siz[i]-1, siz[lf] ) * f(lf) * f( rg)

问题只限于求组合数了
由于n可能大于p,所以只能用Lucas求解

完整代码:

#include<stdio.h>
typedef long long dnt;

int n;
dnt mod;
dnt f[2000010],siz[2000010],saber[2000010],inv[2000010];

void init()
{
    saber[0]=inv[0]=inv[1]=1;
    for(int i=1;i<=n;i++) saber[i]=(saber[i-1]*i)%mod;
    for(int i=2;i<=n;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
    for(int i=1;i<=n;i++) inv[i]=inv[i]*inv[i-1]%mod;
    for(int i=n;i<=2*n+1;i++) f[i]=1;
}

dnt Misaka(dnt a,dnt b)
{
    if(a<b) return 0;
    if(a<mod) return saber[a]*inv[b]%mod*inv[a-b]%mod;
    return Misaka(a/mod,b/mod) * Misaka(a%mod,b%mod)%mod;
}

int main()
{
    scanf("%d%lld",&n,&mod);
    init();
    for(int i=n;i>=1;i--)
    {
        siz[i]=siz[i<<1]+siz[i<<1|1]+1;
        f[i]=Misaka(siz[i]-1,siz[i<<1]) *f[i<<1] %mod *f[i<<1|1]%mod; 
    }
    printf("%lld\n",f[1]);
    return 0;
}
/*
Whoso pulleth out this sword from this stone and anvil is duly born King of all England
*/

嗯,就是这样

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值