Files
python/TangDou/AcWing_TiGao/T6/DiTuiDiGui/97.md
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

228 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

##[$AcWing$ $97$. 约数之和](https://www.acwing.com/problem/content/description/99/)
### 一、题目描述
假设现在有两个自然数 $A$ 和 $B$,$S$ 是 $A^B$ 的所有约数之和。
请你求出 $S~ mod ~9901$ 的值是多少。
**输入格式**
在一行中输入用空格隔开的两个整数 $A$ 和 $B$。
**输出格式**
输出一个整数,代表 $S~ mod~ 9901$ 的值。
**数据范围**
$0≤A,B≤5×10^7$
**输入样例:**
```cpp {.line-numbers}
2 3
```
**输出样例:**
```cpp {.line-numbers}
15
```
注意: $A$ 和 $B$ 不会同时为 $0$。
### 二、理论知识
#### 唯一分解
$$\large N=p_1^{\alpha_1}\cdot p_2^{\alpha_2}\cdot ... \cdot p_k^{\alpha_k}$$
#### 约数个数
$$\large (\alpha_1+1)(\alpha_2+1)...(\alpha_k+1)$$
**证明**:因为$\large p_1^{\alpha_1}$的约数有$\large p_1^0,p_1^1,p_1^2,...,p_1^{\alpha_1}$,共$\large a_1+1$个,同理$p_k^{\alpha_k}$的约数有$\large a_k+1$个,根据乘法原理,就知道了约数的总个数。
#### 约数和
$$\large (1+p_1^1+p_1^2+...+p_1^{\alpha_1})(1+p_2^1+p_2^2+...+p_2^{\alpha_2})...(1+p_n^1+p_n^2+...+p_n^{\alpha_n})$$
**证明**:$p_1^{a_1}$ 的约数有$p_1^{0},p_1^{1},p_1^{2}...p_1^{a_1}$共 $a_1+1$个,$p_2^{a_2}...p_k^{a_k}$ 同理,$n$的一个约数就是在$p_1^{a_1},p_2^{a_2},p_3^{a_3}...p_k^{a_k}$ 每一个的约数中任挑一个组成的,挑法的个数就是约数个数,根据乘法原理他们的和为$(p_1^0+p_1^1+...+p_1^{a_1})(p_2^0+p_2^1+...+p_2^{a_2})...(p_k^0+p_k^1+...+p_k^{a_k})$
**练习题**
[约数之和](https://www.acwing.com/problem/content/description/873/)
[分解质因数](https://www.acwing.com/problem/content/869/)
#### 举栗子: $180=2^2∗3^2∗5^1$
约数个数:$(2+1)(2+1)(1+1)=18$
约数和:$(1+2+4)(1+3+9)(1+5)=546$
### 三、分治法求解等比数列求和
> **巧妙的数学变换**
令$\large sum(p, k)=p^0+p^1+…+p^{k−1}$
* **$k$为偶数**
$$\large sum(p,k)=p^0+p^1+…+p^{k/2−1}+p^{k/2}+p^{k/2+1}+…+p^{k−1} \\
\Rightarrow \\
(p^0+p^1+…+p^{k/2−1})+p^{k/2}∗(p^0+p^1+…+p^{k/2−1}) \\
\Rightarrow \\
sum(p,k/2)+p^{k/2}∗sum(p,k/2) \\
\Rightarrow \\
(p^{k/2}+1)∗sum(p,k/2)
$$
><font color='red' size=4><b>解释:这样我们就得到了一个递推式,
>- $p^{k/2}$ 可以用快速幂计算
>- $sum(p,k/2)$ 由于我们的递推是从小到大推的,在推$sum(p,k)$时,$sum(p,k/2)$已
经算完了,可以直接用。
</b></font>
* **$k$为奇数**
为了更方便调用我们写的偶数项情况,可以单独拿出最后一项,把剩下的项转化为求偶数项的情况来考虑,再加上最后一项,就是奇数项的情况了。也即
$$\large sum(p,k)=p^0+p^1+p^2+...+p^{k-1} \\
= sum(p,k-1)+p^{k-1} $$
当然,也可以拿出来第一项,就是
$$\large sum(p,k)=p^0+p^1+p^2+...+p^{k-1} \\
= p^0+p\times (p^0+p^1+...+p^{k-2}) \\
= 1+ p \times sum(p,k-1)
$$
下面的代码实现中,我将使用拿出最后一项的办法:
```cpp {.line-numbers}
int sum(int p, int k) {
if(k == 1) return 1; //边界
if(k % 2 == 0)
return (LL)(qmi(p, k / 2) + 1) * sum(p, k / 2) % mod;
return (qmi(p, k - 1) + sum(p, k - 1)) % mod;
}
```
**实现代码**
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 9901;
int A, B;
// 分解质因数
unordered_map<int, int> primes; // map存的是数对,key+value,默认按key由小到大排序
void divide(int x) {
for (int i = 2; i * i <= x; i++)
while (x % i == 0) primes[i]++, x /= i;
if (x > 1) primes[x]++;
}
// 快速幂
int qmi(int a, int b) {
int res = 1;
while (b) { // 对b进行二进制分解(我给起的名),枚举b的每一个二进制位
if (b & 1) res = (LL)res * a % mod;
a = (LL)a * a % mod;
b >>= 1;
}
return res;
}
// 分治法求等比数列求和公式
int sum(int p, int k) {
if (k == 1) return 1; // 递归出口
if (k % 2 == 0) // 偶数
return (LL)(qmi(p, k / 2) + 1) * sum(p, k / 2) % mod;
return (qmi(p, k - 1) + sum(p, k - 1)) % mod; // 奇数
}
int main() {
cin >> A >> B;
// 对A分解质因子
divide(A);
int res = 1;
for (auto it : primes) {
// p是质因子,k是质因子的次数
int p = it.first, k = it.second * B; // 约数和公式,需要到每个质因子的it.second次方*B
// res要乘上每一项, 注意这里是k + 1
res = (LL)res * sum(p, k + 1) % mod; // 满满的套路感
}
if (!A) res = 0; // 还要特判A是不是0,注意思考数据边界
cout << res << endl;
return 0;
}
```
时间复杂度 $O(\sqrt{n}lognlogn)$
### 四、等比数列求和公式
$$\large p^0+p^1+p^2+...+p^{k-1}=\frac{p^{k}-1}{p-1}$$
除了分治法外,还可以用 **乘等比数列和公式** 得出分子,再 **乘分母逆元** 的做法:
**快速幂求逆元**
这样的话,就可以愉快地套用高中学的等比数列和公式来求每一项,但这里需要 **特判逆元不存在** 的情况:
* 分母$p-1$是$mod$的倍数,不存在逆元,这时直接乘$(1+p^1+…p^k)\% mod$, 即 $k+1$
* 分母$p-1$不是$mod$的倍数,存在 **逆元**,这是需要用 **快速幂求分子**,再用 **快速幂求分母的逆元**,两者相乘就得到了每一项
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 9901;
int A, B;
//分解质因数
map<int, int> primes; // map存的是数对,key+value,默认按key由小到大排序
void divide(int x) {
for (int i = 2; i <= x / i; i++)
while (x % i == 0) primes[i]++, x /= i;
if (x > 1) primes[x]++;
}
//快速幂
int qmi(int a, int b) {
int res = 1;
while (b) {
if (b & 1) res = (LL)res * a % mod;
a = (LL)a * a % mod;
b >>= 1;
}
return res;
}
int main() {
scanf("%d %d", &A, &B);
//对A分解质因子
divide(A);
int res = 1;
for (auto it : primes) {
// p是质因子,k是质因子的次数
int p = it.first, k = it.second * B;
// res要乘上每一项, 注意这里是k + 1
if ((p - 1) % mod == 0) {
//不存在逆元,由于p-1的是mod的倍数, 故p%mod=1
//所以1 + p + ... + p^k每个数%mod都是1,共k + 1个数,总就是k + 1
res = (LL)res * (k + 1) % mod;
} else
//分子用快速幂计算,注意标准公式和此题的区别,k+1
//分母用费马小定理求逆元 qmi(p-1,mod-2)
res = (LL)res * (qmi(p, k + 1) - 1) % mod * qmi(p - 1, mod - 2) % mod;
}
if (!A) res = 0;
printf("%d\n", (res % mod + mod) % mod);
return 0;
}
```
时间复杂度$O(\sqrt{n}logn)$