洛谷 P1593 因子和 - 数论小杂烩
洛谷 P1593 因子和
题目链接:洛谷 P1593 因子和
算法标签: 数论
,费马小定理
,欧拉定理
,快速幂
题目
题目描述
输入两个正整数a和b,求 \(a^b\) 的因子和。结果太大,只要输出它对9901的余数。
输入格式
仅一行,为两个正整数 \(a\) 和 \(b\) \((0≤a,b≤50000000)\) 。
输出格式
\(a^b\) 的因子和对9901的余数。
输入输出样例
输入 #1
2 3
输出 #1
15
题解:
这道题大概可以理解为 简单数论大杂烩 非常整合的简单数论题,大概涉及几个方面的简单数论知识:
-
整数唯一分解定理
对于任意一个正整数都有且只有一种方式写出其质因子的乘积表达式。
\(A = (p_1 ^ {k_1}*p_2 ^ {k_2}*p_3 ^ {k_3}*\ldots*p_n ^ {k_n})\) [其中 \(p_i\) 均为质数]
-
约数和公式
对于已经被按照整数唯一分解定理分解的一个整数
\(A = (p_1 ^ {k_1}*p_2 ^ {k_2}*p_3 ^ {k_3} *\ldots *p_n ^ {k_n})\)
有 \(A\) 的所有因子之和为:
约数的个数公式:
\(N = (1+k_1)*(1+k_2)*(1+k_3)*\ldots*(1+k_n)\)
-
同余模公式
\((a+b) \% m = (a\%m + b\%m)\%m\)
\((a*b)\%m = (a\%m + b\%m)\%m\)
-
等比数列求和公式
\(sum = \frac{a_1(q^k-1)}{(q-1)}\) [其中 \(a_1\) 为首项,\(q\) 为公比,\(k\) 为项数]
知道这些知识之后就可以 愉快的 分析这道题了,这道题的大致思路就是:
质因子分解 + (等比数列求和 + 乘法逆元) + (费马小定理 / 欧拉定理 + 快速幂)
那么接下来进行分析:
-
如何运用整数唯一分解定理
因为根据整数唯一分解定理 \(A = (p_1 ^ {k_1}*p_2 ^ {k_2}*p_3 ^ {k_3}*\ldots *p_n ^ {k_n})\) ,不难发现,如果要求 \(A^B\) 的乘积表达式,只需要将 \(A\) 的乘积表达式当中所有的 \(k_i\) 都做一次 \(*B\) ,显然所得的表达式可以表示 \(A^B\) 。
-
这道题欧拉定理与费马小定理用法:
在这道题当中可以发现,当我们运用约数和公式的时候,构造出了一个关于多个等比数列和相乘的表达式,由于等比数列求和公式 \(sum = \frac{a_1(q^k-1)}{(q-1)}\) ,在进行快速幂涉及到分母的问题,所以我们就要考虑求分数的模运算,采用求乘法逆元的方法,那么就有一下两种求法:
-
费马小定理求乘法逆元(由于模数9901是一个质数)
ll inverse(ll x,ll p) { return qpow(x, mod - 2);
-
采用扩展欧几里得法(EXGCD)求乘法逆元
ll exgcd(ll a,ll b,ll &x,ll &y) { if(a==0&&b==0) return -1ll; if(b==0){ x=1ll,y=0ll; return a; } ll d=exgcd(b,a%b,y,x); y-=a/b*x; return d; } ll inverse(ll a,ll p) { if(a%p==0) return 1ll; else if((a+1)%p==0) return -2ll; ll x,y,d=exgcd(a,p,x,y); if(d==1) return (x%p+p)%p; return -1ll; }
-
-
本题需要一些特判值:
- 当 \(a = 0\) 的时候无论 \(b\) 为何值,答案一定为 \(0\)
- 当 \(a\not=0\) 并且 $b=0 $ 的时候,答案一定为 \(1\)
- 在模运算时适当先 \(+mod\) 再 \(\% mod\) ,防止出现负数
AC代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll mod = 9901;
const ll maxn = 100010;
ll cnt, ans = 1, prime[maxn], prime_cnt[maxn];
ll qpow(ll q, ll n)
{
ll ans = 1;
while (n)
{
if (n & 1)
ans = ans * q % mod;
n >>= 1;
q = q * q % mod;
}
return ans;
}
ll inverse(ll x,ll p)
{
return qpow(x, mod - 2);
}
int main()
{
ll a, b;
cin >> a >> b ;
if (a == 0) { cout << 0 << endl; return 0; }
if (b == 0) { cout << 1 << endl; return 0; }
for (ll i = 2; i * i <= a; i ++ )
{
if (a % i == 0)
{
prime[ ++ cnt] = i % mod;
while (a % i == 0)
{
a /= i;
prime_cnt[cnt] ++ ;
}
}
}
if (a != 1)
{
prime[ ++ cnt] = a;
prime_cnt[cnt] = 1;
}
for (int i = 1; i <= cnt; i ++ )
{
if (prime[i] % mod == 1)
ans = ans * (prime_cnt[i] + 1) % mod;
else
{
ll div = (prime[i] - 1 + mod) % mod;
ll res1 = qpow(prime[i], prime_cnt[i] * b + 1) % mod - 1;
ll res2 = inverse(div, mod);
ans = ans * (res1 + mod) % mod * res2 % mod;
}
}
cout << ans << endl ;
return 0;
}