整数分块
文章目录
- 整数分块
- 例题1:因数平方和
- 分析:
- 具体代码:
- __int128写法
- 逆元写法
- 例题2:余数之和
- 思想:
- 代码
一般在算法中遇到时间复杂度为
1e9
的, 那么一次 O ( n ) O(n) O(n)的遍历无法解决问题
求== ∑ i = 1 n [ n i ] \sum_{i=1}^n{[\frac{n}{i}]} ∑i=1n[in]==
例题1:因数平方和
原题链接
分析:
要求 n n n的约数,时间复杂度肯定不够, 所以想到反着求
a
是b
的约数
<==> b
是a
的倍数
,所以我们只需要求哪些数包含约数a
相加
每一个约数a
对答案的贡献度为 a 2 a^2 a2, 每个数a
是 n a \frac{n}{a} an个数的约数
故a
这个数对答案的总贡献为 a 2 ∗ [ n a ] a^2\ *\ [\frac{n}{a}] a2 ∗ [an],故答案为:
∑ i = 1 n [ n i ] ∗ i 2 \sum_{i=1}^n\ [\frac{n}{i}]*i^2 i=1∑n [in]∗i2
由上可知, 可以将n
划分为前半段和后半段的话, 可计算出只需操作 2 n 2\sqrt{n} 2n个数即可
如此, 可以将 n n n优化为 2 n 2\sqrt{n} 2n个数进行计算
进行分块治理,如下
将区间长度为 n n n划分为 2 n 2\sqrt{n} 2n个区间, 对每个区间进行求值,每个区间值相同, 只需算连续平方和,可以直接用公式求平方和值, 故每个区间只需要算一次即可
结果: O ( N ) − − > O ( N 2 ) O(N) - - > O(N^2) O(N)−−>O(N2)
推导出:每个区间最大的位置: y = n / x y = n / x y=n/x , 对于各个区间值为== x = n / i x = n / i x=n/i==
即计算区间和每个== [ i , y ] [i, y] [i,y]区间即可, 然后算完一个区间直接 i = y + 1 i = y + 1 i=y+1,来跳跃到下一个区间进行计算, 总共只需要算 2 n 2\sqrt{n} 2n==次
具体代码:
此题在计算平方和时可能数据量会超大(超LL)
__int128写法
#include <iostream>
using namespace std;
const int MOD = 1e9 + 7;
typedef long long LL;
//__int128 : 2^127 - 1
LL calc(int n) { //计算平方和
//这里可能特别大超过2^64(LL),故用__int128临时存储数值return n * (__int128)(n + 1) * (2*n + 1) / 6 % MOD;
}int main() {int n;cin >> n;LL res = 0;for(int i = 1; i <= n; ) {//划分为2sqrt(n)个区间,每个区间的所有数相等,第i个区间值为n/iint x = n / i, y = n / x; //求区间[i, y]的平方和,再乘上x值res = res + x * (calc(y) - calc(i - 1)) % MOD;i = y + 1; }//这块可能取模相减为负值,故cout << (res + MOD) % MOD << endl;return 0;
}
逆元写法
LL calc(int n) { //计算平方和
//这里可能特别大超过2^64(LL),故用__int128临时存储数值// return n * (LL)(n + 1) * (2*n + 1) / 6 % MOD;
//逆元写法return n * (LL)(n + 1) % MOD * (2*n + 1) % MOD * 166666668 % MOD;
}//计算 /6 的逆元
/for(int i = 1; ;i++) { //算出逆元答案为166666668, 带入上式替换掉 '/6'if(i * 6 % MOD == 1) {cout << i << endl;return 0;}
}
例题2:余数之和
原题链接
思想:
首先看到数据范围为1e9级别,故可以想到用分块思想,优化到 O ( 2 n ) O(2\sqrt{n}) O(2n)
k % i k \% i k%i <==> k − [ k i ] ∗ i k - [\frac{k}{i}]*i k−[ik]∗i
则 k % ∑ 1 n k \% \sum_1^n k%∑1n < = = > <==> <==> n ∗ k − ∑ i = 1 n [ k i ] ∗ i n*k\ -\ \sum_{i=1}^n[\frac{k}{i}]*i n∗k − ∑i=1n[ik]∗i
代码
#include <iostream>
using namespace std;
typedef long long LL;
LL sum_primes(int n, int k) {//k % i = k - [k / i] * i ---> k % [1, n] = n*k - k / [1,n]*iLL res = (LL)n * k;for(int i = 1; i <= n; ) {if(k < i) break; //此时往后全为0,不用操作了int x = k / i, y = min(k / x, n); //区间有极限值为n,防止越界//求区间总值 * x --- > 等差数列求和:n * (a1 + an) / 2res -= x * (LL)(y - i + 1) * (i + y) / 2;i = y + 1; //操作下一个区间}return res;
}
int main() {int n, k;cin >> n >> k;cout << sum_primes(n, k) << endl;return 0;
}