73.Acwing基础课第887题-简单-求组合数Ⅲ
73.Acwing基础课第887题-简单-求组合数Ⅲ
题目描述
\(给定 n 组询问,每组询问给定两个整数 a,b,请你输出 C^b_amod\ p的值\)。
输入格式
\(第一行包含整数 n\)。
\(接下来 n 行,每行包含一组 a, b和p\)。
输出格式
共 n 行,每行输出一个询问的解。
数据范围
\(1≤n≤20\),
\(1≤b≤a≤10^{18}\)
\(1≤p≤10^{5}\)
输入样例:
3
5 3 7
3 1 5
6 4 13
输出样例:
3
3
2
代码:
// 包含基础输入输出、算法头文件
#include <iostream>
#include <algorithm>
using namespace std;
// 定义长整型别名:处理超大数(a/b可达1e18,需用LL存储)
typedef long long LL;
// 快速幂函数:计算 (a^k) mod p 的结果(费马小定理求逆元的核心)
// 参数:a-底数,k-指数,p-模数(本题中p是质数)
int qmi(int a, int k, int p)
{
int res = 1; // 初始化结果为1(乘法单位元)
// 二进制分解指数k,时间复杂度O(logk)
while (k)
{
// k&1:判断k的二进制最低位是否为1(等价于k%2==1)
if (k & 1) res = (LL)res * a % p; // 结果乘当前底数,转LL防溢出
a = (LL)a * a % p; // 底数平方,转LL防溢出
k >>= 1; // 指数右移一位(等价于k /= 2)
}
return res; // 返回 (a^k) mod p
}
// 组合数计算函数:求 C(a,b) mod p(a,b < p,p是质数)
// 公式:C(a,b) = a*(a-1)*...*(a-b+1) / (b!) mod p
// 模运算中除法转乘逆元:除以i等价于乘i^(p-2) mod p
int C(int a, int b, int p)
{
// 边界条件:选的数比总数多,组合数为0
if (b > a) return 0;
int res = 1;
// 循环b次,计算分子:a*(a-1)*...*(a-b+1),分母:1*2*...*b
// 技巧:边乘分子边乘分母的逆元,避免先算大数再取模
for (int i = 1, j = a; i <= b; i ++, j -- )
{
res = (LL)res * j % p; // 乘分子项(j从a递减到a-b+1)
// 乘分母项的逆元(i从1递增到b),逆元用快速幂求
res = (LL)res * qmi(i, p - 2, p) % p;
}
return res;
}
// Lucas定理核心函数:求 C(a,b) mod p(a,b可达1e18,p是小质数)
// Lucas定理:C(a,b) ≡ C(a%p, b%p) * C(a/p, b/p) mod p
int lucas(LL a, LL b, int p)
{
// 递归边界:a,b都小于p时,直接用普通组合数公式计算
if (a < p && b < p) return C(a, b, p);
// 递归公式:拆分为 C(a%p, b%p) * C(a/p, b/p) mod p
// 转LL防溢出:C返回int,但相乘可能超int范围
return (LL)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}
int main()
{
int n; // n:查询的组数
cin >> n;
// 处理每组查询
while (n -- )
{
LL a, b; // a:总数;b:选取数(a,b可达1e18)
int p; // p:模数(小质数,如1e5以内)
cin >> a >> b >> p;
// 调用Lucas定理计算并输出结果
cout << lucas(a, b, p) << endl;
}
return 0;
}

浙公网安备 33010602011771号