题目
问题描述
记 为 的所有因数的平方的和。例如:
定义 。给定 , 求 除以 的余数。
输入格式
输入一行包含一个正整数 。
输出格式
输出一个整数表示答案 除以 的余数。
样例输入
100000
样例输出
680584257
评测用例规模与约定
对于 的评测用例, 。
对于 的评测用例, 。
对于所有评测用例, 。
运行限制
- 最大运行时间:1s
- 最大运行内存: 512M
解题
方法一:暴力 枚举 (20%)
思路
最简单直接的暴力做法,对 中每个数求因子平方和并累和。
求一个数的所有因子用到了试除法求约数。
的时间复杂度很明显只能过掉前 的数据( )。
代码
import java.util.*;
public class Main {
static final int MOD = (int) 1e9 + 7;
static long f(int x) {
long res = 0L;
for (long i = 1; i <= x / i; ++i) {
if (x % i == 0) {
res = (res + i * i) % MOD;
if (i * i == x) continue;
long j = x / i;
res = (res + j * j) % MOD;
}
}
return res;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
long ans = 0;
for (int i = 1; i <= n; ++i) ans = (ans + f(i)) % MOD;
System.out.println(ans);
}
}
方法二:数学 (30% ~ 100%)
思路
以 为例:
很明显的发现 所有数都以因子的形式出现,只是出现的次数有所不同,所以我们可以从因子的角度来理解问题:
- 因子 出现在 ,共 个数中;
- 因子 出现在 ,共 个数中;
- 因子 出现在 ,共 个数中;
- 因子 出现在 ,共 个数中;
- 因子 出现在 ,共 个数中;
- 因子 出现在 ,共 个数中;
- 因子 出现在 ,共 个数中;
- …
- 因子 出现在 ,共 个数中。
很符合直觉,也很符合数学地总结出规律:因子 在 中出现在 的倍数中,出现次数为 ,所以因子 可以对 提供的贡献为 。
那么就有: 。
推到这一步,我们把时间复杂度降到了 ,可以过前 的数据( )了。
代码
import java.util.*;
import java.io.*;
public class Main {
static final int MOD = (int) 1e9 + 7;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
long ans = 0L;
for (long i = 1; i <= n; ++i) {
ans = (ans + (n / i) * (i * i)) % MOD;
}
System.out.println(ans);
}
}
优化
观察 ,当 时:
很容易发现 是按块状分布的,对于元素相同的 一块(例见上) :
其中: 的平方和可以由平方和公式: 很容易的以 的时间复杂度算出来。
但是我们注意到,分式的被除数中 都是可以取到 的大数,所以每乘一次都应该对 取模以防止越界。但这是一个除法,如果被除数取模后再除会导致结果错误,所以应该利用 的逆元把除法变为乘法再计算。
对于 ,如何求 ?根据数论分块的结论:
对于常数 ,使得式子 成立的最大的满足 的 的值为 。
即值 所在的块的右端点为 。
时间复杂度: ,可以过 的数据( )。
代码
import java.util.*;
import java.io.*;
public class Main {
static final int MOD = (int) 1e9 + 7;
static long inv;
// 快速幂用于求逆元
static long quickPow(long base, long exp, long mod) {
long res = 1L;
while (exp > 0) {
if ((exp & 1) == 1) res = (res * base) % MOD;
base = (base * base) % MOD;
exp >>= 1;
}
return res;
}
// 平方和公式 每一步都要取模
static long s(long n) {
return n * (n + 1) % MOD * (2 * n + 1) % MOD * inv % MOD;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
long ans = 0L;
// 求6在模MOD意义下的逆元
inv = quickPow(6, MOD - 2, MOD);
for (int l = 1; l <= n;) {
// x: 当前块中的相同值 r: 当前块的右边界
int x = n / l, r = n / (n / l);
ans = ((ans + x * (s(r) - s(l - 1))) % MOD + MOD) % MOD;
l = r + 1;
}
System.out.println(ans);
}
}
是常量, 的逆元自然也是常量,直接硬编码也可以:
import java.util.*;
import java.io.*;
public class Main {
static final int MOD = (int) 1e9 + 7;
static final int INV = 166666668;
static long s(long n) {
return n * (n + 1) % MOD * (2 * n + 1) % MOD * INV % MOD;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
long ans = 0L;
for (int l = 1; l <= n;) {
int x = n / l, r = n / (n / l);
ans = ((ans + x * (s(r) - s(l - 1))) % MOD + MOD) % MOD;
l = r + 1;
}
System.out.println(ans);
}
}
评论区