量子二叉堆 动态规划+乱搞求逆元

34 篇文章 0 订阅
24 篇文章 1 订阅

【一句话题意】最后希望你给出n个互不相同的数能构成多少个不同的大小为n的二叉堆(大根堆或小根堆都算二叉堆,不同定义为至少有一个位置上数不同)。
n<=5e6

【分析】对于二叉堆,顶端最小(或最大)是确定的,剩余元素的分配其实是随意的。将剩余的元素随意分配到左右两棵子树,离散化之后就变成了之前的一个已求得的子问题。可以得到一个(伪)状态转移方程
f [ x ] = f [ x 左 子 树 节 点 数 ] ∗ f [ x 右 子 树 节 点 数 ] ∗ C x − 1 左 ( 右 ) 子 树 节 点 数 f[x]=f[x左子树节点数]*f[x右子树节点数]*C^{左(右)子树节点数}_{x-1} f[x]=f[x]f[x]Cx1

关键在于组合数的快速处理,可以用一个前缀积维护阶乘,那么一次递推的复杂度就是求一次逆元的复杂度(logn)。

然而毒瘤的出题人还是把这种算法给卡掉了。听说因为递推到n会有一定的无用状态,所以乱搞+记忆化搜索可以卡过(雾)

其实可以先求n的逆元在O(n)逆着乘回去,就可以快速得到逆元了。(强啊,yzf)

【code】

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=5e6+1000;
const LL mod=1e9+7;
int n,ans=0;
int lt[maxn],rt[maxn];
LL f[maxn],mul[maxn],inv[maxn];
LL pw(register LL x,register LL p){
	register LL ret=1;
	while(p>0){
		if(p&1) ret=ret*x%mod;
		x=x*x%mod,p>>=1;
	}
	return ret;
}
inline LL C(register LL a,register LL b){
	return mul[b]*inv[b-a]%mod*inv[a]%mod;
}
int main(){
	cin>>n;
	f[1]=1,f[2]=1;
	register int l=1,r=1,fl=0;
	for(register int i=3;i<=n;i++){
		lt[i]=l,rt[i]=r;
		if(fl){
			r++;if(r==l) fl=0;
		}
		else{
			l++;if(l>(r<<1)) fl=1;
		}
	}
	mul[0]=1;
	for(register int i=1;i<=n;i++)
		mul[i]=mul[i-1]*i%mod;
	inv[n]=pw(mul[n],mod-2);
	for(register int i=n-1;i>=1;i--)
		inv[i]=inv[i+1]*(i+1)%mod;
	for(register int i=3;i<=n;i++)
		f[i]=f[lt[i]]*f[rt[i]]%mod*C(rt[i],i-1)%mod;
	ans=(f[n]<<1)%mod;
	if(n==1) cout<<1<<endl;
	else cout<<ans<<endl;
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值