【题解】Luogu-P2480 [SDOI2010]古代猪文

这篇博客探讨了如何利用欧拉定理、Lucas定理和中国剩余定理(CRT)高效地计算模999911659下正整数n的组合数之和,并给出了解决该问题的代码实现。文章详细介绍了算法思路,包括快速幂优化指数计算和使用Lucas定理分治计算每个模素因子下的余数,最后通过CRT合并结果。
摘要由CSDN通过智能技术生成

P2480 [SDOI2010]古代猪文

Description \text{Description} Description

给定正整数 n , g n,g n,g,求 g ∑ k ∣ n C n k   m o d   999911659 g^{\sum_{k\mid n}C_{n}^{k}}\bmod 999911659 gknCnkmod999911659

  • 对于 100 % 100\% 100% 的数据, 1 ≤ n , g ≤ 1 0 9 1\le n,g \le 10^9 1n,g109

Solution \text{Solution} Solution

前置芝士:

  • 欧拉定理
  • L u c a s \rm Lucas Lucas 定理
  • C R T \rm CRT CRT

由欧拉定理知 g ∑ k ∣ n C n k ≡ g ( ∑ k ∣ n C n k )   m o d     φ ( 999911659 ) ≡ g ( ∑ k ∣ n C n k )   m o d     999911658 ( m o d 999911659 ) g^{\sum_{k\mid n}C_{n}^{k}}\equiv g^{(\sum_{k\mid n}C_{n}^{k})\bmod\ \varphi(999911659)}\equiv g^{(\sum_{k\mid n}C_{n}^{k})\bmod\ 999911658}\pmod{999911659} gknCnkg(knCnk)mod φ(999911659)g(knCnk)mod 999911658(mod999911659)

指数可以快速幂 O ( log ⁡ 999911658 ) O(\log 999911658) O(log999911658) 解决,找因数是 Θ ( n ) \Theta(\sqrt{n}) Θ(n ) 的。

于是问题转化为求 C n k   m o d   999911658 C_{n}^{k}\bmod 999911658 Cnkmod999911658

直接算是不行的,但是直接 L u c a s \rm Lucas Lucas 因为模数太大也不行。

999911658 = 2 × 3 × 4679 × 35617 999911658=2×3×4679×35617 999911658=2×3×4679×35617,令 x = C n k x=C_{n}^{k} x=Cnk,考虑分别算出
{ x   m o d   2 x   m o d   3 x   m o d   4679 x   m o d   35617 \begin{cases} x\bmod 2\\ x\bmod 3\\ x\bmod 4679\\ x\bmod 35617 \end{cases} xmod2xmod3xmod4679xmod35617
这个就可以用 L u c a s \rm Lucas Lucas

再用 C R T \rm CRT CRT 合并即可。

Code \text{Code} Code

//18 = 9 + 9 = 18.
#include <iostream>
#include <cstdio>
typedef long long ll;
using namespace std;

const int MAXN = 35620;
const int MOD = 999911659;

int qpow(int a, int b, int p)
{
	int base = a, ans = 1;
	while (b)
	{
		if (b & 1)
		{
			ans = (ll)ans * base % p;
		}
		base = (ll)base * base % p;
		b >>= 1;
	}
	return ans;
}

int inv(int a, int p)
{
	return qpow(a, p - 2, p);
}

int fac[MAXN], inv_fac[MAXN];

void init(int p)
{
	fac[0] = 1;
	for (int i = 1; i < p; i++)
	{
		fac[i] = (ll)fac[i - 1] * i % p;
	}
	inv_fac[p - 1] = inv(fac[p - 1], p);
	for (int i = p - 2; i >= 0; i--)
	{
		inv_fac[i] = (ll)inv_fac[i + 1] * (i + 1) % p;
	}
}

int C(int n, int m, int p)
{
	if (m > n)
	{
		return 0;
	}
	return (ll)fac[n] * inv_fac[n - m] % p * inv_fac[m] % p;
}

int Lucas(int n, int m, int p)
{
	if (!m)
	{
		return 1;
	}
	return (ll)Lucas(n / p, m / p, p) * C(n % p, m % p, p) % p;
}

const int a[5] = {0, 2, 3, 4679, 35617};
int b[5];

int CRT()
{
	int m = MOD - 1, ans = 0;
	for (int i = 1; i <= 4; i++)
	{
		int mi = m / a[i];
		int Mi = inv(mi, a[i]);
		ans = (ans + (ll)b[i] * mi * Mi) % m;
	}
	return ans;
}

int main()
{
	int n, g;
	scanf("%d%d", &n, &g);
	if (!(g % MOD))
	{
		puts("0");
		return 0;
	}
	for (int i = 1; i <= 4; i++)
	{
		init(a[i]);
		for (int j = 1; j * j <= n; j++)
		{
			if (!(n % j))
			{
				b[i] = (b[i] + Lucas(n, j, a[i])) % a[i];
				if (j * j != n) //注意完全平方数
				{
					b[i] = (b[i] + Lucas(n, n / j, a[i])) % a[i];
				}
			}
		}
	}
	printf("%d\n", qpow(g, CRT(), MOD));
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值