HOJ 3240 Counting Binary Trees(卡特兰数,扩欧求逆元,经典)

49 篇文章 0 订阅
9 篇文章 0 订阅
这篇博客介绍了如何利用扩展欧几里得算法求解卡特兰数模逆元的问题。文章详细阐述了卡特兰数的递推公式,并解析了在大数环境下如何高效计算每个卡特兰数对特定模数m的余数。通过因数分解解决分子分母不互质的问题,最后展示了如何应用扩展欧几里得算法求解分母的逆元。
摘要由CSDN通过智能技术生成

卡特兰数,扩欧求逆元
题目意思:
n个节点的二叉树,可以构成有 catalan[n] 种不同的二叉树。 sum[n] 是卡特兰数前 n项的总和。
每次给出两个数 n, m, 求 sum[n] % m 是多少。
本题要点:
1、n很大,如果用大数来存第 100000 个卡特兰数,肯定超时。
2、卡特兰数的 递推公式
h[i] = h[i - 1] * (4 * i - 2) / (i + 1)
要求每一个卡特兰数 对m的模, 这里有除法,需要 求 (i + 1) 在模 m的情况下的逆元。
3、扩展欧几里得算法,求逆元
同余式 ax = 1(mod m), 如果 a, m 互质, 那么必然存在x, 使得 ax = 1(mod m)成立。
x 就是 a 在模 m下的逆元。 式子 ax = 1(mod m), 变形 ax - m*y = 1, 右扩展欧几里得算法,
直接求出x。
4、 本题中, (4 * i - 2) 与 m 不一定互质, (i + 1) 与 m 不一定互质,通过 m 进行因数分解来解决:
m 进行因数分解:
vector vc; // 存放 m 的各个素因子
5、 观察 式子 h[i] = h[i - 1] * (4 * i - 2) / (i + 1), 第一个卡特兰数 h[1] = 1
然后全部展开的话, h[i] = 1 * (6 / 3) * (10 / 4) * (14 / 5) * … * ( (4 * i - 2) / (i + 1) )
分子部分 (4 * i - 2) 所有的乘积,必然能够整除 所有 分母部分 (i + 1) 的所有乘积。
然后对于分子 temp = (4 * i - 2) ,如果能整除m的某个素因子 vc[j], temp /= vc[j], 同时 cnt[j]++;
假设除剩下来的 是 fz , 那么 fz 与 m 互质。
然后对于分母 temp = (i + 1) ,如果能整除m的某个素因子 vc[j], temp /= vc[j], 同时 cnt[j]–;
假设除剩下来的 是 fm , 那么 fm 与 m 互质。 可以用扩展欧几里得算法, 求 fm 在模m下的逆元了。
对于每一个 cnt[j], 不会有负数的。具体实现看代码,比较明了。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
const int MaxN = 100010;
typedef long long LL;
LL n, m;
int cnt[MaxN];	//存放相应的次数
vector<LL> vc;	// 存放 m 的各个素因子

LL exgcd(LL a, LL b, LL&x, LL& y)
{
	if(!b)
	{
		x = 1, y = 0;
		return a;
	}
	LL d = exgcd(b, a % b, x, y);
	LL z = x; x = y; y = z - y * (a / b);
	return d;
}

LL inv(LL a, LL mod)	// 求a在模 mod 的情况下的逆元
{						//逆元不存在的话,返回 -1	
	LL d, x, y;
	d = exgcd(a, mod, x, y);
	return d == 1? (x + mod) % mod : -1;
}

void getPrime(int x)
{
	for(int i = 2; i * i <= x; ++i)
	{
		if(x % i)	
			continue;
		while(x % i == 0)
			x /= i;
		vc.push_back(i);
	}
	if(x > 1)
		vc.push_back(x);
}

int main()
{
	while(scanf("%lld%lld", &n, &m) != EOF)
	{
		if(n == 0 && m == 0)
			break;
		vc.clear();
		memset(cnt, 0, sizeof cnt);
		LL res = 1 % m, pre = 1;
		getPrime(m);
		for(int i = 2; i <= n; ++i)
		{
			LL fz = 4 * i - 2, fm = i + 1;
			int len = vc.size();	//处理 fz
			for(int j = 0; j < len; ++j)
			{
				if(fz % vc[j] == 0)
				{
					while(fz % vc[j] == 0)
					{
						fz /= vc[j];
						cnt[j]++;
					}
				}
			}
			pre = pre * fz % m;
			for(int j = 0; j < len; ++j)
			{
				if(fm % vc[j] == 0)
				{
					while(fm % vc[j] == 0)
					{
						fm /= vc[j];
						cnt[j]--;
					}
				}
			}
			if(fm > 1)
				pre = pre * inv(fm, m) % m;
			LL tmp = pre;
			for(int j = 0; j < len; ++j)
			{
				for(int k = 1; k <= cnt[j]; ++k)
				{
					tmp = (tmp * vc[j]) % m;
				}
			}
			res = (res + tmp) % m;
		}
		printf("%lld\n", res);
	}
	return 0;
}

/*
3 100
4 10
0 0
*/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值