'commit'
This commit is contained in:
172
TangDou/AcWing/DP/Tree/求约数和的三重境界.md
Normal file
172
TangDou/AcWing/DP/Tree/求约数和的三重境界.md
Normal file
@@ -0,0 +1,172 @@
|
||||
## 求约数和的三重境界
|
||||
|
||||
|
||||
### 一、先上结论
|
||||
|
||||
<!-- 让表格居中显示的风格 -->
|
||||
<style>
|
||||
.center
|
||||
{
|
||||
width: auto;
|
||||
display: table;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
</style>
|
||||
<div class="center">
|
||||
|
||||
| 数据量/办法 | 暴力$O(N^2)$ | 普通筛法$O(N\cdot logN)$ | 欧拉筛法$O(N)$ |
|
||||
| ---- | ---- | ---- | ---- |
|
||||
| $n=1e5$ | $13402ms$ | $4ms$ | $2ms$ |
|
||||
| $n=1e6$ | 无法忍受,不能出结果 | $67ms$ | $15ms$ |
|
||||
|
||||
</div>
|
||||
|
||||
考虑到$O(N^2)$的复杂度,按$C++$一秒$1e8$的运算能力,$1e6*1e6=1e12$,应该是需要$1e12 /1e8=1e4=10000$秒,三个小时,去死吧!太垃圾了~
|
||||
|
||||
<font color='red' size=4><b>注意:
|
||||
* 普通筛法:代码极短,性能足够,推荐使用
|
||||
* 欧拉筛法:代码太长,性能优秀,不推荐使用
|
||||
|
||||
</b></font>
|
||||
|
||||
|
||||
### 二、暴力法
|
||||
|
||||
```c++
|
||||
//暴力大法 , 不包含自己
|
||||
int bforce(int n) {
|
||||
int sum = 0;
|
||||
for (int i = 1; i < n; i++) {
|
||||
if (n % i == 0)
|
||||
sum = sum + i;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
```
|
||||
时间复杂度$O(N^2)$
|
||||
|
||||
|
||||
### 三、普通筛法
|
||||
```c++
|
||||
for (int i = 1; i <= n; i++) // O(n)
|
||||
for (int j = 2; j <= n / i; j++) // 调和级数O(logn) = n + n / 2 + n / 3 + ... + n/n = lnn + c
|
||||
sum2[i * j] += i;
|
||||
```
|
||||
时间复杂度$O(N\cdot logN)$
|
||||
|
||||
### 四、线性筛法
|
||||
[原理解析](https://www.cnblogs.com/littlehb/p/16650121.html)
|
||||
|
||||
```c++
|
||||
int primes[N], cnt; // primes[]存储所有素数
|
||||
bool st[N]; // st[x]存储x是否被筛掉
|
||||
int num[N]; // i的最小质因子构成的一个等比数列求和的结果
|
||||
int sd[N]; //约数和
|
||||
void get_primes(int n) {
|
||||
sd[1] = 1; // 1的约数和是1
|
||||
|
||||
for (int i = 2; i <= n; i++) { //遍历 2~n,筛质数的同时,筛出每个数字的约数和
|
||||
if (!st[i]) { //如果i是质数
|
||||
primes[cnt++] = i; //记录i到质数数组中
|
||||
sd[i] = num[i] = 1 + i; //质数的约数和是1+自身
|
||||
}
|
||||
for (int j = 0; primes[j] * i <= n; j++) {
|
||||
st[i * primes[j]] = true; //记录primes[j]的整数倍(i倍)是合数
|
||||
|
||||
if (i % primes[j]) { //如果i不包含primes[j]这个质数因子
|
||||
sd[i * primes[j]] = sd[i] * sd[primes[j]];
|
||||
num[i * primes[j]] = primes[j] + 1;
|
||||
} else { //如果i包含了primes[j]这个质数因子
|
||||
sd[i * primes[j]] = sd[i] / num[i] * (num[i] * primes[j] + 1);
|
||||
num[i * primes[j]] = num[i] * primes[j] + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 五、附:测试对比的代码
|
||||
```c++
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
typedef long long LL;
|
||||
|
||||
const int N = 1e6 + 10;
|
||||
int sum1[N], sum2[N], sd[N];
|
||||
int n = 1e5;
|
||||
|
||||
//暴力大法 , 不包含自己
|
||||
int bforce(int n) {
|
||||
int sum = 0;
|
||||
for (int i = 1; i < n; i++) {
|
||||
if (n % i == 0)
|
||||
sum = sum + i;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
int primes[N], cnt; // primes[]存储所有素数
|
||||
bool st[N]; // st[x]存储x是否被筛掉
|
||||
int num[N]; // i的最小质因子构成的一个等比数列求和的结果
|
||||
int sd[N]; //约数和
|
||||
void get_primes(int n) {
|
||||
sd[1] = 1; // 1的约数和是1
|
||||
|
||||
for (int i = 2; i <= n; i++) { //遍历 2~n,筛质数的同时,筛出每个数字的约数和
|
||||
if (!st[i]) { //如果i是质数
|
||||
primes[cnt++] = i; //记录i到质数数组中
|
||||
sd[i] = num[i] = 1 + i; //质数的约数和是1+自身
|
||||
}
|
||||
for (int j = 0; primes[j] * i <= n; j++) {
|
||||
st[i * primes[j]] = true; //记录primes[j]的整数倍(i倍)是合数
|
||||
|
||||
if (i % primes[j]) { //如果i不包含primes[j]这个质数因子
|
||||
sd[i * primes[j]] = sd[i] * sd[primes[j]];
|
||||
num[i * primes[j]] = primes[j] + 1;
|
||||
} else { //如果i包含了primes[j]这个质数因子
|
||||
sd[i * primes[j]] = sd[i] / num[i] * (num[i] * primes[j] + 1);
|
||||
num[i * primes[j]] = num[i] * primes[j] + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
//记录开始时间
|
||||
clock_t begin = clock();
|
||||
// 1、暴力计算 O(n^2)
|
||||
// for (int i = 1; i <= n; i++) sum1[i] = bforce(i);
|
||||
// for (int i = 1; i <= n; i++) printf("%d ", sum1[i]);
|
||||
puts("");
|
||||
//记录结束时间
|
||||
clock_t end = clock();
|
||||
cout << "Running time:" << (double)(end - begin) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
|
||||
|
||||
// 2、通过筛法求出1到n的所有约数之和
|
||||
//记录开始时间
|
||||
begin = clock();
|
||||
for (int i = 1; i <= n; i++) // O(n)
|
||||
for (int j = 2; j <= n / i; j++) // 调和级数O(logn) = n + n / 2 + n / 3 + ... + n/n = lnn + c
|
||||
sum2[i * j] += i;
|
||||
|
||||
// for (int i = 1; i <= n; i++) printf("%d ", sum2[i]);
|
||||
puts("");
|
||||
//记录结束时间
|
||||
end = clock();
|
||||
cout << "Running time:" << (double)(end - begin) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
|
||||
|
||||
// 3、利用欧拉定理筛出约数和
|
||||
//记录开始时间
|
||||
begin = clock();
|
||||
get_primes(n);
|
||||
// for (int i = 1; i <= n; i++) printf("%d ", sd[i]-i);
|
||||
puts("");
|
||||
//记录结束时间
|
||||
end = clock();
|
||||
cout << "Running time:" << (double)(end - begin) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user