参考资料:oi-wiki 莫比乌斯反演 |oi-wiki 筛法 |oi-wiki 欧拉函数 |blog |blog
[CQOI2007]余数求和 题目链接
这道题主要是练习数论分块 。
减号后面的值进行数论分块,每一块是常数 $\left\lfloor\frac{k}{l}\right\rfloor$ 乘以一个等差数列求和 $l+\cdots+r=\frac{(l+r)(r-l+1)}{2}$。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <algorithm> #include <cstdio> using namespace std;long long n, k, ans;int main () { scanf ("%lld%lld" , &n, &k); ans = 1ll * n * k; for (long long l = 1 , r; l <= n; l = r + 1 ){ if (k / l == 0 ) r = n; else r = min (n, k / (k / l)); ans -= (k / l) * (l + r) * (r - l + 1 ) / 2 ; } printf ("%lld\n" , ans); return 0 ; }
[POI2007]ZAP-Queries 题目链接
接下来进行数论分块,每一块是常数 $\left\lfloor\frac{n}{kl}\right\rfloor\left\lfloor\frac{m}{kl}\right\rfloor$ 乘上 $\mu(l)+\cdots+\mu(r)$,前缀和预处理即可。
这里 $r$ 的计算:$\left\lfloor\frac{n}{kl}\right\rfloor=\left\lfloor\frac{n}{kr}\right\rfloor\leqslant\frac{n}{kr}\implies r\leqslant\frac{\frac{n}{k}}{\left\lfloor\frac{n}{kl}\right\rfloor}\implies r=\left\lfloor\frac{\frac{n}{k}}{\left\lfloor\frac{n}{kl}\right\rfloor}\right\rfloor$.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <bits/stdc++.h> using namespace std;typedef long long LL;const int N = 50005 ;int k;int mu[N], pList[N], pID, muS[N];bool notP[N];void Euler (int n) { notP[0 ] = notP[1 ] = 1 ; mu[1 ] = 1 ; for (int i = 1 ; i <= n; i++){ if (notP[i] == 0 ) pList[++pID] = i, mu[i] = -1 ; for (int j = 1 ; j <= pID; j++){ if (1ll * i * pList[j] > n) break ; notP[i * pList[j]] = 1 ; if (i % pList[j] == 0 ){ mu[i * pList[j]] = 0 ; break ; } else mu[i * pList[j]] = -mu[i]; } } for (int i = 1 ; i <= n; i++) muS[i] = muS[i-1 ] + mu[i]; } LL calc (int n, int m) { LL res = 0 ; for (int l = 1 , r; l <= min (n / k, m / k); l = r + 1 ){ r = min ((n / k) / ((n / k) / l), (m / k) / ((m / k) / l)); res += 1ll * (muS[r] - muS[l-1 ]) * (n / k / l) * (m / k / l); } return res; } int main () { Euler (50000 ); int T; for (scanf ("%d" , &T); T; T--){ int a, b; scanf ("%d%d%d" , &a, &b, &k); printf ("%lld\n" , calc (a, b)); } return 0 ; }
[HAOI2011]Problem b 题目链接
通过二维前缀和就可以把问题转换成上一道题。
不能乱开 long long
,否则常数太大会 TLE
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <bits/stdc++.h> using namespace std;typedef long long LL;const int N = 50005 ;int k;int mu[N], pList[N], pID, muS[N];bool notP[N];void Euler (int n) { notP[0 ] = notP[1 ] = 1 ; mu[1 ] = 1 ; for (int i = 1 ; i <= n; i++){ if (notP[i] == 0 ) pList[++pID] = i, mu[i] = -1 ; for (int j = 1 ; j <= pID; j++){ if (1ll * i * pList[j] > n) break ; notP[i * pList[j]] = 1 ; if (i % pList[j] == 0 ){ mu[i * pList[j]] = 0 ; break ; } else mu[i * pList[j]] = -mu[i]; } } for (int i = 1 ; i <= n; i++) muS[i] = muS[i-1 ] + mu[i]; } LL calc (int n, int m) { LL res = 0 ; for (int l = 1 , r; l <= min (n / k, m / k); l = r + 1 ){ r = min ((n / k) / ((n / k) / l), (m / k) / ((m / k) / l)); res += 1ll * (muS[r] - muS[l-1 ]) * (n / k / l) * (m / k / l); } return res; } int main () { Euler (50000 ); int T; for (scanf ("%d" , &T); T; T--){ int a, b, c, d; scanf ("%d%d%d%d%d" , &a, &b, &c, &d, &k); printf ("%lld\n" , calc (b, d) - calc (a-1 , d) - calc (b, c-1 ) + calc (a-1 , c-1 )); } return 0 ; }
[SPOJ5971]LCMSUM 题目链接
令 $g(n)=\sum\limits_{d\mid n}d\cdot\varphi(d)$,如果我们可以预处理出 $g(n)$,那么我们就可以 $O(1)$ 回答了。我们这样预处理:枚举 $i$,将所有 $i\mid n$ 的 $g(n)$ 加上贡献 $i\cdot\varphi(i)$,这样预处理的复杂度是调和级数,其阶为 $O(n\lg n)$. 【这种预处理方式很重要! 】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <bits/stdc++.h> using namespace std;typedef long long LL;const int N = 1000005 ;int T, n;int phi[N], pList[N], pID;bool notP[N];LL g[N]; void Euler (int n) { notP[0 ] = notP[1 ] = 1 ; phi[1 ] = 1 ; for (int i = 1 ; i <= n; i++){ if (notP[i] == 0 ) pList[++pID] = i, phi[i] = i - 1 ; for (int j = 1 ; j <= pID; j++){ if (1ll * i * pList[j] > n) break ; notP[i * pList[j]] = 1 ; if (i % pList[j] == 0 ){ phi[i * pList[j]] = phi[i] * pList[j]; break ; } else phi[i * pList[j]] = phi[i] * (pList[j] - 1 ); } } } int main () { Euler (1000000 ); for (int i = 1 ; i <= 1000000 ; i++) for (int j = 1 ; i * j <= 1000000 ; j++) g[i*j] += 1ll * i * phi[i]; for (scanf ("%d" , &T); T; T--){ scanf ("%d" , &n); printf ("%lld\n" , (g[n] + 1 ) * n / 2 ); } return 0 ; }
[国家集训队]Crash的数字表格 题目链接
我们设
它是可以通过数论分块 计算的,每一块是常数 $\frac{\left(\left\lfloor\frac{n}{l}\right\rfloor+1\right)\left\lfloor\frac{n}{l}\right\rfloor}{2}\cdot\frac{\left(\left\lfloor\frac{m}{l}\right\rfloor+1\right)\left\lfloor\frac{m}{l}\right\rfloor}{2}$ 乘上 $l^2\mu(l)+\cdots+r^2\mu(r)$,前缀和预处理即可。
那么原式可写作:
而它又是一个可以数论分块 计算的,每一块是常数 $S\left(\left\lfloor\frac{n}{l}\right\rfloor,\left\lfloor\frac{m}{l}\right\rfloor\right)$ 乘上 $l+\cdots+r=\frac{(l+r)(r-l+1)}{2}$.
所以这道题是数论分块的嵌套 ,复杂度为 $O(\sqrt n\cdot\sqrt n)=O(n)$.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <bits/stdc++.h> using namespace std;typedef long long LL;const int N = 10000005 ;const LL MOD = 20101009 ;int mu[N], pList[N], pID;bool notP[N];LL sum[N]; void Euler (int n) { notP[0 ] = notP[1 ] = 1 ; mu[1 ] = 1 ; for (int i = 1 ; i <= n; i++){ if (notP[i] == 0 ) pList[++pID] = i, mu[i] = -1 ; for (int j = 1 ; j <= pID; j++){ if (1ll * i * pList[j] > n) break ; notP[i * pList[j]] = 1 ; if (i % pList[j] == 0 ){ mu[i * pList[j]] = 0 ; break ; } else mu[i * pList[j]] = -mu[i]; } } for (int i = 1 ; i <= n; i++) sum[i] = (sum[i-1 ] + 1ll * i * i % MOD * mu[i] % MOD) % MOD; } LL S (int n, int m) { LL res = 0 ; if (n > m) swap (n, m); for (int l = 1 , r; l <= n; l = r + 1 ){ r = min (n / (n / l), m / (m / l)); res += 1ll * (1 + n / l) * (n / l) % MOD * (1 + m / l) % MOD * (m / l) % MOD * ((sum[r] - sum[l-1 ]) % MOD + MOD) % MOD * 15075757 % MOD; res %= MOD; } return res; } int n, m;LL ans; int main () { Euler (10000000 ); scanf ("%d%d" , &n, &m); if (n > m) swap (n, m); for (int dl = 1 , dr; dl <= n; dl = dr + 1 ){ dr = min (n / (n / dl), m / (m / dl)); ans += (1ll * (dl + dr) * (dr - dl + 1 ) % MOD * 10050505 % MOD) * S (n / dl, m / dl) % MOD; ans %= MOD; } printf ("%lld\n" , ans); return 0 ; }
其实这道题还能继续优化:先鸽一下(雾
[luoguP2398]GCD SUM 题目链接
解法一 :和上一道题差不多。
数论分块嵌套。
复杂度:$O(n)$
解法二 :
数论分块。
复杂度:$O(\sqrt n)$ (不算线性筛)
注:其实解法一继续推导可得解法二的表达式,不过推导的下一步不是很显然(要卷回去):
这个博客 讲的也是这个,并给这种方法起了个名字叫做欧拉反演,哈哈。
解法一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <bits/stdc++.h> using namespace std;const int N = 100005 ;int mu[N], pList[N], pID;bool notP[N];void Euler (int n) { notP[0 ] = notP[1 ] = 1 ; mu[1 ] = 1 ; for (int i = 1 ; i <= n; i++){ if (notP[i] == 0 ){ pList[++pID] = i; mu[i] = -1 ; } for (int j = 1 ; j <= pID; j++){ if (1ll * i * pList[j] > n) break ; notP[i * pList[j]] = 1 ; if (i % pList[j] == 0 ){ mu[i * pList[j]] = 0 ; break ; } else mu[i * pList[j]] = -mu[i]; } } } int n, muS[N];int main () { Euler (100000 ); for (int i = 1 ; i <= 100000 ; i++) muS[i] = muS[i-1 ] + mu[i]; scanf ("%d" , &n); long long ans = 0 ; for (int dl = 1 , dr; dl <= n; dl = dr + 1 ){ dr = n / (n / dl); long long res = 0 ; for (int l = 1 , r; l <= n / dl; l = r + 1 ){ r = (n / dl) / (n / dl / l); res += 1ll * (muS[r] - muS[l-1 ]) * (n / dl / l) * (n / dl / l); } ans += 1ll * (dl + dr) * (dr - dl + 1 ) / 2 * res; } printf ("%lld\n" , ans); return 0 ; }
解法二:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <bits/stdc++.h> using namespace std;const int N = 100005 ;int phi[N], pList[N], pID;bool notP[N];void Euler (int n) { notP[0 ] = notP[1 ] = 1 ; phi[1 ] = 1 ; for (int i = 1 ; i <= n; i++){ if (notP[i] == 0 ){ pList[++pID] = i; phi[i] = i - 1 ; } for (int j = 1 ; j <= pID; j++){ if (1ll * i * pList[j] > n) break ; notP[i * pList[j]] = 1 ; if (i % pList[j] == 0 ){ phi[i * pList[j]] = phi[i] * pList[j]; break ; } else phi[i * pList[j]] = phi[i] * (pList[j] - 1 ); } } } int n;long long phiS[N];int main () { Euler (100000 ); for (int i = 1 ; i <= 100000 ; i++) phiS[i] = phiS[i-1 ] + phi[i]; scanf ("%d" , &n); long long ans = 0 ; for (int l = 1 , r; l <= n; l = r + 1 ){ r = n / (n / l); ans += 1ll * (phiS[r] - phiS[l-1 ]) * (n / l) * (n / l); } printf ("%lld\n" , ans); return 0 ; }
[NOI2010]能量采集 题目链接
题目要求的就是
和上一题一样了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <bits/stdc++.h> using namespace std;const int N = 100005 ;int phi[N], pList[N], pID;bool notP[N];void Euler (int n) { notP[0 ] = notP[1 ] = 1 ; phi[1 ] = 1 ; for (int i = 1 ; i <= n; i++){ if (notP[i] == 0 ){ pList[++pID] = i; phi[i] = i - 1 ; } for (int j = 1 ; j <= pID; j++){ if (1ll * i * pList[j] > n) break ; notP[i * pList[j]] = 1 ; if (i % pList[j] == 0 ){ phi[i * pList[j]] = phi[i] * pList[j]; break ; } else phi[i * pList[j]] = phi[i] * (pList[j] - 1 ); } } } int n, m;long long phiS[N];int main () { Euler (100000 ); for (int i = 1 ; i <= 100000 ; i++) phiS[i] = phiS[i-1 ] + phi[i]; scanf ("%d%d" , &n, &m); if (n > m) swap (n, m); long long ans = 0 ; for (int l = 1 , r; l <= n; l = r + 1 ){ r = min (n / (n / l), m / (m / l)); ans += 1ll * (phiS[r] - phiS[l-1 ]) * (n / l) * (m / l); } printf ("%lld\n" , 2 * ans - 1ll * n * m); return 0 ; }
[SDOI2015]约数个数和 题目链接
引理:
证明:约数个数公式:$d(n)=d\left(\prod\limits_{i=1}^k{p_i}^{r_i}\right)=\prod\limits_{i=1}^k(r_i+1)$。考虑 $ij$ 的一个质因子 $p$,设 $i=i’\cdot{p}^{r_i}$ ,$j=j’\cdot{p}^{r_j}$,则 $p$ 对答案有 $r_i+r_j+1$ 的贡献。又考虑右边的式子,由于 $\gcd(x,y)=1$ 的约束,$p$ 只能从 $i$ 或 $j$ 其中一边取出,或者不取出,贡献仍是 $r_i+r_j+1$。证毕。
于是:
$S(1…n)$ 可以通过数论分块 $O(n\sqrt n)$ 地预处理出来,随后上式可通过数论分块 $O(\sqrt n)$ 地回答。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <bits/stdc++.h> using namespace std;const int N = 50005 ;int mu[N], pList[N], pID;bool notP[N];void Euler (int n) { notP[0 ] = notP[1 ] = 1 ; mu[1 ] = 1 ; for (int i = 1 ; i <= n; i++){ if (notP[i] == 0 ){ pList[++pID] = i; mu[i] = -1 ; } for (int j = 1 ; j <= pID; j++){ if (1ll * i * pList[j] > n) break ; notP[i * pList[j]] = 1 ; if (i % pList[j] == 0 ){ mu[i * pList[j]] = 0 ; break ; } else mu[i * pList[j]] = -mu[i]; } } } int T, n, m, muS[N];long long S[N];int main () { Euler (50000 ); for (int i = 1 ; i <= 50000 ; i++) muS[i] = muS[i-1 ] + mu[i]; for (int i = 1 ; i <= 50000 ; i++){ for (int l = 1 , r; l <= i; l = r + 1 ){ r = i / (i / l); S[i] += (r - l + 1 ) * (i / l); } } for (scanf ("%d" , &T); T; T--){ scanf ("%d%d" , &n, &m); if (n > m) swap (n, m); long long ans = 0 ; for (int dl = 1 , dr; dl <= n; dl = dr + 1 ){ dr = min (n / (n / dl), m / (m / dl)); ans += 1ll * (muS[dr] - muS[dl-1 ]) * S[n / dl] * S[m / dl]; } printf ("%lld\n" , ans); } return 0 ; }
[luoguP2257]YY的GCD 题目链接
最后一步的代换非常重要 !详细逻辑如下:
然后令 $g(T)=\sum\limits_{p\mid T}{\mu\left(\frac{T}{p}\right)}\quad(p\in\text{prime})$,这是可以预处理 的,即枚举质数 $p$,将 $p\mid n$ 的 $g(n)$ 加上贡献 $\mu\left(\frac{n}{p}\right)$,复杂度是 $O(n\cdot H(n))\sim O(n\lg n)$
随后数论分块,每一块是常数 $\left\lfloor\frac{n}{l}\right\rfloor\left\lfloor\frac{m}{l}\right\rfloor$ 乘上 $g(l)+\cdots+g(r)$,前缀和预处理即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <bits/stdc++.h> using namespace std;const int N = 10000005 ;int mu[N], pList[N], pID;bool notP[N];void Euler (int n) { notP[0 ] = notP[1 ] = 1 ; mu[1 ] = 1 ; for (int i = 1 ; i <= n; i++){ if (notP[i] == 0 ){ pList[++pID] = i; mu[i] = -1 ; } for (int j = 1 ; j <= pID; j++){ if (1ll * i * pList[j] > n) break ; notP[i * pList[j]] = 1 ; if (i % pList[j] == 0 ){ mu[i * pList[j]] = 0 ; break ; } else mu[i * pList[j]] = -mu[i]; } } } long long g[N];int main () { Euler (10000000 ); for (int i = 1 ; i <= pID; i++) for (int j = pList[i]; j <= 10000000 ; j += pList[i]) g[j] += mu[j / pList[i]]; for (int i = 1 ; i <= 10000000 ; i++) g[i] += g[i-1 ]; int T; for (scanf ("%d" , &T); T; T--){ int n, m; scanf ("%d%d" , &n, &m); if (n > m) swap (n, m); long long ans = 0 ; for (int l = 1 , r; l <= n; l = r + 1 ){ r = min (n / (n / l), m / (m / l)); ans += 1ll * (g[r] - g[l-1 ]) * (n / l) * (m / l); } printf ("%lld\n" , ans); } return 0 ; }
[luoguP2586]GCD 题目链接
求的东西和上一道题基本一样,但是没有多组数据,时限加紧。
注意上一道题为了 $O(\sqrt n)$ 地回答而进行了 $O(n\lg n)$ 的预处理,这个预处理在这道题里会超时。但是这道题只需要 $O(n)$ 回答就行了。
重新推一波式子:
预处理 $\varphi(n)$ 的前缀和,就可以 $O(n)$ 解决了。
上面的推导中最妙的一点是用上了 $\varphi(n)=\sum\limits_{i=1}^n[\gcd(i,n)=1]$ 这个定义式 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <bits/stdc++.h> using namespace std;const int N = 10000005 ;int phi[N], pList[N], pID;bool notP[N];void Euler (int n) { notP[0 ] = notP[1 ] = 1 ; phi[1 ] = 1 ; for (int i = 1 ; i <= n; i++){ if (notP[i] == 0 ){ pList[++pID] = i; phi[i] = i - 1 ; } for (int j = 1 ; j <= pID; j++){ if (1ll * i * pList[j] > n) break ; notP[i * pList[j]] = 1 ; if (i % pList[j] == 0 ){ phi[i * pList[j]] = phi[i] * pList[j]; break ; } else phi[i * pList[j]] = phi[i] * (pList[j] - 1 ); } } } long long phiS[N];int main () { Euler (10000000 ); for (int i = 1 ; i <= 10000000 ; i++) phiS[i] = phiS[i-1 ] + phi[i]; int n; scanf ("%d" , &n); long long ans = 0 ; for (int i = 1 ; pList[i] <= n; i++) ans += 2 * phiS[n / pList[i]] - 1 ; printf ("%lld\n" , ans); return 0 ; }