异想之旅:本人原创博客完全手敲,绝对非搬运,全网不可能有重复;本人无团队,仅为技术爱好者进行分享,所有内容不牵扯广告。本人所有文章仅在CSDN和个人博客(一定是异想之旅域名)发布,除此之外全部是盗文!
输入两个整数 a a a 和 b b b,求 a b a^b ab 的因子和。
由于结果太大,只要输出它对 9901 9901 9901 取模的结果。
仅一行,为两个整数 a a a 和 b b b。
输出一行一个整数表示答案对 9901 9901 9901 取模的结果。
In 1:
2 3
Out 1:
15
对于全部的测试点,保证 1 ≤ a ≤ 5 × 1 0 7 1 \leq a \leq 5 \times 10^7 1≤a≤5×107, 0 ≤ b ≤ 5 × 1 0 7 0 \leq b \leq 5 \times 10^7 0≤b≤5×107。
本文视频讲解:
【洛谷P1593】因子和视频讲解
为了完成洛谷P1593这道题也是拼了……用到了三个不会的知识:因子和公式和等比数列求和公式和费马小定理求分数
这里另摆一份很好的题解:洛谷 [P1593 因子和] {快速幂+费马小定理求逆元+求解质因子} 奋斗的珂珂~ - 代码先锋网
先摆出因子和公式的参考资料:
等比数列定义很简单,设有数列 [ a 1 , a 2 , . . . , a n ] [a_1, a_2,...,a_n] [a1,a2,...,an] ,存在一个定值 q q q 使得任意 1 ≤ i ≤ n − 1 1 ≤ i ≤ n-1 1≤i≤n−1 都有 a i + 1 = a i × q a_{i+1} = a_i \times q ai+1=ai×q 。由此定义可知 a n = a 1 × q n − 1 a_n = a_1 \times q^{n-1} an=a1×qn−1 。
这个数列的求和公式为 S n = a 1 + a 2 + . . . + a n = a 1 q n − 1 q − 1 S_n = a_1 + a_2 + ... + a_n = a_1 \frac{q^n-1}{q-1} Sn=a1+a2+...+an=a1q−1qn−1 ,下面我们来证明这个公式:
∵ q S n = q a 1 + q a 2 + . . . + q a n = a 2 + a 3 + . . . + a n + 1 ∵ qS_n = qa_1 + qa_2 + ... + qa_n = a_2 + a_3 + ... + a_{n+1} ∵qSn=qa1+qa2+...+qan=a2+a3+...+an+1
∴ q S n − S n = ( q − 1 ) S n = a n + 1 − a 1 = ( a 1 ∗ q n ) − a 1 = a 1 ( q n − 1 ) ∴ qS_n - S_n= (q-1) S_n = a_{n+1} - a_1 = (a_1 * q^n) - a_1 = a_1(q^n-1) ∴qSn−Sn=(q−1)Sn=an+1−a1=(a1∗qn)−a1=a1(qn−1)
∴ S n = a 1 q n − 1 q − 1 ∴ S_n = a_1 \frac{q^n-1}{q-1} ∴Sn=a1q−1qn−1
(参考资料:等比数列公式及推导_高三网)
费马小定理求分数快速幂的公式及证明:
(来源:分数取模(费马小定理)_give it a try-CSDN博客_分数取模运算)
根据整数的唯一分解定理,整数a进行质因数分解对应的式子唯一,有:
a = p 1 k 1 ∗ p 2 k 2 ∗ p 3 k 3 ∗ … ∗ p n k n a = p_1^{k_1} * p_2^{k_2} *p_3^{k_3}* … * p_n^{k_n} a=p1k1∗p2k2∗p3k3∗…∗pnkn
又因为本题要分解的是 a b a^b ab,所以上面的式子又可以写成这样:
a b = p 1 k 1 ∗ b ∗ p 2 k 2 ∗ b ∗ p 3 k 3 ∗ b ∗ … ∗ p n k n ∗ b a^b= p_1^{k_1*b} * p_2^{k_2*b} *p_3^{k_3*b}* … * p_n^{k_n*b} ab=p1k1∗b∗p2k2∗b∗p3k3∗b∗…∗pnkn∗b
证明很简单,就是把上面第一个式子乘上 b b b 次即可得第二个式子
接下来我们要求的是因子和,所以就有:
a n s = ( 1 + p 1 1 + p 1 2 + p 1 3 + … + p 1 k 1 ∗ b ) ∗ ( 1 + p 2 1 + p 2 2 + p 2 3 + … + p 1 k 2 ∗ b ) ∗ . . . ∗ ( 1 + p n 1 + p n 2 + p n 3 + … + p n k n ∗ b ) ans= (1+p_1^1 + p_1^2 +p_1^3+ … + p_1^{k_1*b})*(1+p_2^1 + p_2^2 +p_2^3+ … + p_1^{k_2*b})*...*(1+p_n^1 + p_n^2 +p_n^3+ … + p_n^{k_n*b}) ans=(1+p11+p12+p13+…+p1k1∗b)∗(1+p21+p22+p23+…+p1k2∗b)∗...∗(1+pn1+pn2+pn3+…+pnkn∗b)
于是求和转化成了等比数列的和的乘积
而对于每一个等比数列,可根据公式求和: s u m = p n + 1 − 1 p − 1 sum=\frac{p^{n+1}-1}{p-1} sum=p−1pn+1−1 (上面推得的等比数列求和公式中代入 a 1 = 1 a_1 = 1 a1=1 ,自然有了 n = n + 1 n=n+1 n=n+1 )
注意这里的 p p p 与上面截图中的 p p p 含义不一样!这里指的是公比(类比等差数列的公差),上面指的是 mod
(快速幂中取余的那个数)
有了这些知识来看代码:
#include
using namespace std;
const int mod = 9901;
int ys[50000000];
// 一个不那么简单的快速幂模板
long long ksm(long long x, long long y) {
if (y == 0) return 1;
if (y % 2 == 0) return ((long long)pow(ksm(x, y / 2) % mod, 2) % mod);
return (x * (long long)pow(ksm(x, y / 2) % mod, 2) % mod);
}
int main() {
int a, b;
cin >> a >> b;
int aa = a; // 由于aa的值会被修改,所以需要一个变量保存
ys[2] = 0;
while (a % 2 == 0) {
a /= 2;
ys[2]++;
}
ys[2] *= b; // 因为是b次幂因此要乘以b
for (int i = 3; i <= a / i; i += 2) {
ys[i] = 0;
while (a % i == 0) {
a /= i;
ys[i]++;
}
ys[i] *= b;
}
if(a != 1) {
// 分解质因数,若有质因数超过根号a,则只能是a本身
ys[a] = b;
}
long long ans = 1;
for (int i = 2; i <= aa; i++) {
if (!ys[i]) continue; // 不存在该质因数
int temp;
if (i % mod == 1) {
temp = (ys[i] + 1) % mod; // 详见博文最后的解释
} else
temp = ((ksm(i, ys[i] + 1) - 1 + mod) % mod) * ksm(i - 1, mod - 2) % mod; // 使用费马小定理求解
if (temp != 0) ans = ans * temp % mod;
}
cout << ans << endl;
return 0;
}
关于代码第42行特判 i % mod == 1
(也就是判断 i-1
是否为 mod
的倍数;同样适用于45行 temp != 0
特判):
当 i-1
为 mod
的倍数时,等比数列中每一项取余 9901 9901 9901 的结果都为 1 1 1 ,等比数列的和便是 k i × b ( 上 方 证 明 公 式 中 的 表 示 ) = y s [ i ] ( 代 码 中 的 表 示 ) k_i \times b(上方证明公式中的表示) = ys[i](代码中的表示) ki×b(上方证明公式中的表示)=ys[i](代码中的表示) ,因此此处特判改为 temp = ys[i] + 1
;同理,若 i
为 mod
的倍数,则 temp
值为 0 0 0 恒成立(洛谷测试点中不存在此极端情况因此不考虑也能AC)