题目
我们知道包含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就是结果
}
}