ZCMU 1934: ly的二叉树【Catalan数】【大数取模求逆元】【快速幂】

ZCMU 1934: ly的二叉树

Time Limit: 1 Sec  Memory Limit: 128 MB

Description

某一天,ly正在上数据结构课。老师在讲台上面讲着二叉树,ly在下面发着呆。
突然ly想到一个问题:对于一棵n个无编号节点的有根二叉树,有多少种形态呐?你能告诉她吗?

Input

多组输入,处理到文件结束
每一组输入一行,一个正整数n(1≤n≤1000000),意义如题目所述。

Output

每组数据输出一行,包含一个正整数表示答案,由于数字可能非常大,你只需要把最后的结果对1000000007取模即可。

Sample Input

3

Sample Output

5

思路

一开始看到这道题我是懵逼的,经过一个上午和一个中午的猛肝,终于从CSDN各大大佬们的相关博客中得到启发,成功AC。

这是一道以离散数学结论为幌子,考察大数取模运算中除法取模处理的题,在此总结一下自己的理解。

用结论

本题要用到二叉树形态的Catalan数结论:
n个节点的二叉树形态求解递推公式:h(n)=\frac{h(n-1)*(4*n-2)}{(n+1)},其中h(0)=0,h(1)=1。
 本题数据过大,需要按题目要求对1000000007取模,但是在四则运算中,在除法运算时取模不等于对除法运算结果取模,由于本题表达式中又含有除法,因此无法直接求出。

求逆元

可将对商取模转换为对被除数和其除数关于1000000007的逆元,即:

(a / b) % p = (a % p * inv(b) % p) % p

将除数用其逆元替换,将除法转为乘法可得出不爆表的结果。

求逆元的公式为:

a关于b的逆元等于a^(b-2)

又由于这题的b很大.......坑爹啊   导致常规的从1到b循环相乘很慢,并且没算几次就会爆表,所以我们有:

快速幂算法

因为十进制数b可以用二进制表示为:b=b_{0}*2^{^{0}}+b_{1}*2^{^{1}}+b_{2}*2^{^{2}}+b_{3}*2^{^{3}}+...+b_{n}*2^{^{n}}其中bn表示b的二进制形式中的第n位。(其实就是个二进制转十进制公式)

故a的b次方可以表示为a^{b}=a^{b_{0}*2^{^{0}}+b_{1}*2^{^{1}}+b_{2}*2^{^{2}}+b_{3}*2^{^{3}}+...+b_{n}*2^{^{n}}}=a^{b_{0}*2^{0}}*a^{b_{1}*2^{1}}*a^{b_{2}*2^{2}}*a^{b_{4}*2^{4}}*...*a^{b_{n}*2^{n}}

这就是快速幂算法的理论公式了。

我们能够发现如果b的某一位是0的话,那么那一位所在的a因子为1,不必计算,因此可以加入一个if排除它。

我们还能够发现在二进制里每一位的数要么是0要么是1,也就是说a公式的每个因子,如果当前因子的b非零,其值都是前一个因子在b非0条件下值的平方,所以我们完全可以用一个几行的循环来算出每一个因子,并把它们取模乘起来。实现代码如下:

//快速幂算法

long long int quickpow(long long int a, long long int b)
{
	a %= MOD;//每次都记得取模
	//res用于记录结果
	long long int res = 1;
	for (; b; b >>= 1)//b右移一位,本质上是对b的二进制数每一个数进行遍历
	{
		//b和1做与运算,本质上就是判断b的二进制形式上最右位是否为1,是则结果为1,否则为0
		if (b & 1)//如果b的二进制末位不为0
		{
			res = (res*a) % MOD;
		}
		a = (a*a) % MOD;//每次记录本次a因子中b不为0情况下的值,用于下一次循环的求解
	}
	return res;
}

在解决方法问题之后,接下来就是用一个循环将1到1000000的所有结果全部肝出来,每次输入直接输出对应结果,美滋滋。

那么接下来就上代码:

AC代码

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstdio>
#include <vector>
#define MOD 1000000007
int ans[1000001];

using namespace std;

//快速幂

long long int quickpow(long long int a, long long int b)
{
	a %= MOD;//每次都记得取模
	//res用于记录结果
	long long int res = 1;
	for (; b; b >>= 1)//b右移一位,本质上是对b的二进制数每一个数进行遍历
	{
		//b和1做与运算,本质上就是判断b的二进制形式上最右位是否为1,是则结果为1,否则为0
		if (b & 1)//如果b的二进制末位不为0
		{
			res = (res*a) % MOD;
		}
		a = (a*a) % MOD;//每次记录本次a因子中b不为0情况下的值,用于下一次循环的求解
	}
	return res;
}

//求逆元

long long int inv(long long int a, long long int b)
{
	return quickpow(a, b - 2);
}

int main()
{
	long long int n, i;
	ans[0] = 0; ans[1] = 1;
	for (i = 2; i <= 1000000; i++)
	{
		ans[i] = ans[i - 1] * (4 * i - 2) % MOD*inv(i + 1, MOD) % MOD;
	}
	while(scanf("%lld",&n)!=EOF)
	{
		printf("%d\n", ans[n]);
	}
	system("pause");
	return 0;
}

猜你喜欢

转载自blog.csdn.net/x948675238/article/details/81155614