第九届蓝桥杯JavaB组省赛-堆的计数

题目

我们知道包含N个元素的堆可以看成是一棵包含N个节点的完全二叉树。
每个节点有一个权值。对于小根堆来说,父节点的权值一定小于其子节点的权值。

假设N个节点的权值分别是1~N,你能求出一共有多少种不同的小根堆吗?

例如对于N=4有如下3种:

1
/ \
2 3
/
4

1
/ \
3 2
/
4

1
/ \
2 4
/
3

由于数量可能超过整型范围,你只需要输出结果除以1000000009的余数。

【输入格式】
一个整数N。
对于40%的数据,1 <= N <= 1000
对于70%的数据,1 <= N <= 10000
对于100%的数据,1 <= N <= 100000

【输出格式】
一个整数表示答案。

【输入样例】
4

【输出样例】
3

资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 1000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
不要使用package语句。不要使用jdk1.7及以上版本的特性。
主类的名字必须是:Main,否则按无效代码处理。

思路:动态规划+快速幂+乘法逆元解决组合数

首先这道题肯定用动态规划,从后往前递增的计算可能的数量。状态转移方程为:dp[i]=dp[i*2]*dp[i*2+1]*C(num[i]-1,num[i*2]),其中C()用于计算组合数。这个方程是这样来的,首先从当前节点的所有子节点数为num[i]-1,从这些中,随机挑出来num[i*2]个给左子树,组合数为C(num[i]-1,num[i*2]),然后剩下的给右子树。这只是确定了当前节点左右子树有哪些元素,没有计算左右子树的组合。左子树有dp[i*2]中排列顺序,右子树有dp[i*2+1]中排列顺序。
第二个大问题就是如何计算数据规模那么大的组合数,我们知道组合数公式为:
在这里插入图片描述
但是1/ m!不好计算出来,n!除m!(n-m)!也是不好算的,因为结果都取了模。我们正好可以用乘法逆元来解决,不懂的可以去搜一下,这里就不赘述了。

这里我们用快速幂来计算,这里再来回顾一下快速幂的模板:

long mpow(int a,int b)
{
	int ans=1;
	while(b!=0)
	{
		if((b&1)==1)
			ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}

1/m!为mpow(caches[i],mod-2);,剩下的一顿操作就出来了。

代码:

public class test_01 {
	static int mod=1000000009;
	 static long[] caches;
	private static long[] lucas;
	public static void main(String[] args) {
		Scanner in=new Scanner(System.in);
		int N=in.nextInt();
		int[] num=new int[N+1];    //num[i]表示i这个点有多少个儿子
		long[] dp=new long[N+1];   //do[i]表示i有多少种不同的儿子组合
		caches = new long[N+1];
		lucas = new long[N+1];
		caches[0]=1;
		lucas[0]=1;
		for(int i=1;i<=N;i++)
		{
			caches[i]=caches[i-1]*i%mod;   //caches[i]表示i的阶乘
			lucas[i]=mpow(caches[i],mod-2);  //lucas[i]表示为i的阶乘分之一,1/i! 等价于 i!的mod-2次方  费马小定理
		}
		//计算出每个节点的孩子数
		for(int i=N;i>0;i--)
			if(i*2+1<=N)
				num[i]+=num[i*2+1]+num[i*2]+1;
			else if(i*2<=N)
				num[i]+=num[i*2]+1;
			else
				num[i]=1;
		//开始计算dp
		for(int i=N;i>0;i--)
		{
			if(i*2+1<=N)
				dp[i]=dp[i*2+1]*dp[i*2]%mod*C(num[i]-1,num[i*2])%mod;
			else
				dp[i]=1;
		}
		System.out.println(dp[1]);
	}
	 static long C(int i, int j) {
	 //C(i,j)==i!*(1/j!)*(1/(i-j)!)  也就是组合数公式
		return caches[i]*lucas[j]%mod*lucas[i-j]%mod;
	}
	private static long mpow(long i, long j) {
	//快速幂
		long ans=1;
		while(j!=0)
		{
			if((j&1)==1)
				ans=ans*i%mod;
			i=i*i%mod;
			j>>=1;
		}
		return ans;
	}
}

扩展欧几里得算法求乘法逆元

最近学习数论,学到扩展欧几里得算法。那么就过来更新一波吧…
如果看不到没思路,就是看看关于扩展欧几里得算法就行。这只是补充…


public class Main{
	static int x,y;
	//扩展欧几里得算法
	static int extend_gcd(int a,int b)
	{
		if(b==0)
		{
			x=1;
			y=0;
			return a;
		}
		else {
			int res=extend_gcd(b,a%b);
			int t=y;
			y=x-(a/b)*y;
			x=t;
			return res;
		}
	}
	static int f(int a,int b,int m) 
	{
		int d=extend_gcd(a, b);   //返回的d是gcd(a,b)
		if(m%d!=0)
			System.out.println("无解");
		long mul=m/d;
		x*=mul;    //乘m和gcd(a,b)的相差的倍数
		y*=mul;
		return d;
	}
	public static void main(String[] args)
	{
			//a的乘法逆元是h,那么a*h%mod=1%mod => a*x+b*mod=1  x是a的乘法逆元 解方程,求出x即可
			f(1240,1000000007,1);   //求出1240的乘法逆元
		System.out.println(x);      //x就是结果
	}
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值