From a2ce73c6b6cc9883b5886f9f1f2d2e0900b37e10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sat, 16 Dec 2023 09:16:46 +0800 Subject: [PATCH 01/25] 'commit' --- TangDou/Topic/mobius_Prepare.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 TangDou/Topic/mobius_Prepare.cpp diff --git a/TangDou/Topic/mobius_Prepare.cpp b/TangDou/Topic/mobius_Prepare.cpp new file mode 100644 index 0000000..129277d --- /dev/null +++ b/TangDou/Topic/mobius_Prepare.cpp @@ -0,0 +1,20 @@ +#include +using namespace std; +int main() { + int n = 100; + for (int i = 1; i <= n; i++) { + printf("F(%d)=", i); + bool f = 1; + for (int j = 1; j <= n; j++) { + if (i % j == 0) { // 如果j是i的因子的话,输出f(j) + if (f) { + printf("f(%d)", j); + f = 0; + } else + printf("+f(%d)", j); + } + } + printf("\n"); + } + return 0; +} \ No newline at end of file From a678869ebed8e7a13e03c1fc68e1bced75975e83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sat, 16 Dec 2023 11:14:28 +0800 Subject: [PATCH 02/25] 'commit' --- TangDou/Topic/Mobius/mobius.cpp | 82 +++++++++++ TangDou/Topic/{ => Mobius}/mobius_Prepare.cpp | 1 + TangDou/Topic/mobius.cpp | 81 ---------- TangDou/Topic/莫比乌斯函数.md | 139 ++++++------------ 4 files changed, 125 insertions(+), 178 deletions(-) create mode 100644 TangDou/Topic/Mobius/mobius.cpp rename TangDou/Topic/{ => Mobius}/mobius_Prepare.cpp (85%) delete mode 100644 TangDou/Topic/mobius.cpp diff --git a/TangDou/Topic/Mobius/mobius.cpp b/TangDou/Topic/Mobius/mobius.cpp new file mode 100644 index 0000000..f2d3dbb --- /dev/null +++ b/TangDou/Topic/Mobius/mobius.cpp @@ -0,0 +1,82 @@ +#include + +using namespace std; +#define int long long +#define endl "\n" + +const int N = 1e5 + 10; +int n = 20; + +// 筛法求莫比乌斯函数(枚举约数) +int mu[N], sum[N]; // sum[N]:梅滕斯函数,也就是莫比乌斯函数的前缀和 +int primes[N], cnt; +bool st[N]; +void get_mobius(int n) { + mu[1] = 1; + for (int i = 2; i <= n; i++) { + if (!st[i]) { + primes[cnt++] = i; + mu[i] = -1; + } + for (int j = 0; primes[j] <= n / i; j++) { + int t = primes[j] * i; + st[t] = true; + if (i % primes[j] == 0) { + mu[t] = 0; + break; + } + mu[t] = -mu[i]; + } + } + // 维护u(x)前缀和:梅滕斯函数 + for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i]; +} + +// 最简筛法求莫比乌斯函数(枚举倍数) +void get_mobius_beishu(int x) { + mu[1] = 1; + for (int i = 1; i <= x; i++) + for (int j = i + i; j <= x; j += i) + mu[j] -= mu[i]; +} + +// 单个数的莫比乌斯函数 +int getmob(int x) { + int sum = 0; + for (int i = 2; i <= x / i; i++) { // 从2开始,一直到 sqrt(x),枚举所有可能存的小因子 + int cnt = 0; + if (x % i == 0) { // 如果x可以整除i + while (x % i == 0) cnt++, x /= i; // 计数,并且不断除掉这个i因子 + if (cnt > 1) return 0; // 如果某个因子,存在两个及以上个,则返回0 + sum++; // 记录因子个数 + } + } + if (x != 1) sum++; // 如果还存在另一个大因子,那么因子个数+1 + return (sum & 1) ? -1 : 1; // 奇数个因子,返回-1,否则返回1 +} + +void printIdx(int n) { + for (int i = 1; i <= n; i++) printf("%2lld ", i); + cout << endl; +} + +signed main() { + // 计算单个数字的莫比乌斯函数 + for (int i = 1; i <= n; i++) printf("%2lld ", getmob(i)); + cout << endl; + + // 枚举约数的筛法 + get_mobius(n); + for (int i = 1; i <= n; i++) printf("%2lld ", mu[i]); + cout << endl; + + // 清空一下,继续测试 + memset(mu, 0, sizeof mu); + + // 枚举倍数的筛法 + get_mobius_beishu(n); + for (int i = 1; i <= n; i++) printf("%2lld ", mu[i]); + cout << endl; + + printIdx(n); +} \ No newline at end of file diff --git a/TangDou/Topic/mobius_Prepare.cpp b/TangDou/Topic/Mobius/mobius_Prepare.cpp similarity index 85% rename from TangDou/Topic/mobius_Prepare.cpp rename to TangDou/Topic/Mobius/mobius_Prepare.cpp index 129277d..3753eac 100644 --- a/TangDou/Topic/mobius_Prepare.cpp +++ b/TangDou/Topic/Mobius/mobius_Prepare.cpp @@ -1,6 +1,7 @@ #include using namespace std; int main() { + // 讨论F(i)与f(i)的所有因子之间的关联关系 int n = 100; for (int i = 1; i <= n; i++) { printf("F(%d)=", i); diff --git a/TangDou/Topic/mobius.cpp b/TangDou/Topic/mobius.cpp deleted file mode 100644 index 97aa2da..0000000 --- a/TangDou/Topic/mobius.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include - -using namespace std; - -typedef long long LL; -const int N = 1e5 + 10; -int n = 20; - -// 筛法求莫比乌斯函数(枚举约数) -LL mu[N], sum[N]; -int primes[N], cnt; -bool st[N]; -void get_mobius2(LL n) { - mu[1] = 1; - for (LL i = 2; i <= n; i++) { - if (!st[i]) { - primes[cnt++] = i; - mu[i] = -1; - } - for (LL j = 0; primes[j] <= n / i; j++) { - LL t = primes[j] * i; - st[t] = true; - if (i % primes[j] == 0) { - mu[t] = 0; - break; - } - mu[t] = mu[i] * -1; - } - } - // 维护u(x)前缀和 - for (LL i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i]; -} - -// 最简筛法求莫比乌斯函数(枚举倍数) -void get_mobius1(LL x) { - mu[1] = 1; - for (LL i = 1; i <= x; i++) - for (LL j = i + i; j <= x; j += i) - mu[j] -= mu[i]; -} - -// 单个数的莫比乌斯函数 -int getmob(LL x) { - int sum = 0; - for (LL i = 2; i <= x / i; i++) { // 从2开始,一直到 sqrt(x),枚举所有可能存的小因子 - int cnt = 0; - if (x % i == 0) { // 如果x可以整除i - while (x % i == 0) cnt++, x /= i; // 计数,并且不断除掉这个i因子 - if (cnt > 1) return 0; // 如果某个因子,存在两个及以上个,则返回0 - sum++; // 记录因子个数 - } - } - if (x != 1) sum++; // 如果还存在另一个大因子,那么因子个数+1 - return (sum & 1) ? -1 : 1; // 奇数个因子,返回-1,否则返回1 -} - -int main() { - // 计算单个数字的莫比乌斯函数 - for (int i = 1; i <= n; i++) printf("%2d ", getmob(i)); - cout << endl; - for (int i = 1; i <= n; i++) printf("%2d ", i); - - // 筛法求莫比乌斯函数 - // get_mobius1(n); - // for (int i = 1; i <= n; i++) - // cout << "mu1[" << i << "]=" << mu[i] << endl; - - // //清空一下,继续测试 - // memset(mu, 0, sizeof mu); - - // //测试枚举约数的筛法 - // get_mobius2(n); - - // for (int i = 1; i <= n; i++) { - // //计算单个数字的莫比乌斯函数 - // cout << "mu2[" << i << "]=" << getmob(i) << endl; - // cout << "mu2[" << i << "]=" << mu[i] << endl; - // cout << "========================================" << endl; - // } - return 0; -} \ No newline at end of file diff --git a/TangDou/Topic/莫比乌斯函数.md b/TangDou/Topic/莫比乌斯函数.md index 35b2f2b..dabd364 100644 --- a/TangDou/Topic/莫比乌斯函数.md +++ b/TangDou/Topic/莫比乌斯函数.md @@ -1,39 +1,13 @@ -## 莫比乌斯函数 -https://blog.csdn.net/qq_49593247/article/details/120394226 - -https://www.cnblogs.com/letlifestop/p/10262757.html +## 莫比乌斯函数专题 -// https://blog.csdn.net/qq_42887171/article/details/95237740 +### 一、莫比乌斯函数 -要学习莫比乌斯函数需要学习 到 **积性函数**,深度理解 **欧拉筛** 。 +先明确一点,莫比乌斯函数并不是什么很高大上的东西,它其实只是一个由容斥系数所构成的函数。 -### 一、积性函数 +**[视频讲解:两分钟学会墨比乌斯函数](https://www.bilibili.com/video/BV12P4y1Q7tc/)** #### 1. 定义 -**积性函数**:若$gcd(a,b)=1$,且满足$f(a*b)=f(a)*f(b)$,则称$f(x)$为 **积性函数** - -**完全积性函数**:对于任意正整数$a,b$,都满足$f(a*b)=f(a)*f(b)$,则称$f(x)$为 **完全积性函数** - -#### 2. 性质 - -1. 若$f(n),g(n)$均为积性函数,则函数$h(n)=f(n)*g(n)$也是积性函数 - -2. 若$f(n)$为积性函数,则函数$c*f(n)$($c$是常数)也是积性函数,反之一样 - -3. **任何积性函数都能应用线性筛**,在$O(n)$时间内求出$1\sim n$项(**莫比乌斯函数要用到**),像素数,欧拉函数等都是 **积性函数**。 - - -### 二、莫比乌斯函数 - -#### 1. 用途 - -我们举例一道经典的应用题,求$1$到$N$中与$a$互质的数的个数:根据容斥原理,设$S_i$为$1$到$N$中和$a$有公因子$i$的数的个数,答案为$N−S_2-S_3-S_5-S_7...+S_{2,3}+S_{3,5}+S_{2,5}...$,可以发现,其答案为$\large \displaystyle \sum_{i=1}^{N}\mu(i)*S_i$。 - -> **注**:在使用容斥原理解决计数问题时,莫比乌斯函数的值就是每一项的系数,要么是$1$,要么是$-1$。这是因为莫比乌斯函数在容斥原理中的作用就是用来表示每个子集合的权重,从而在计算中起到排除重复计数的作用。 - -#### 2. 定义 - ![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202312151650942.png) 莫比乌斯函数是个分段函数,它的意思: @@ -50,103 +24,71 @@ https://www.cnblogs.com/letlifestop/p/10262757.html 函数值 1 -1 -1 0 -1 1 -1 0 0 1 -1 0 -1 1 1 0 -1 0 -1 0 数 字: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ``` + +#### 2. 用途 + +我们举例一道经典的应用题,求$1$到$N$中与$a$互质的数的个数:根据容斥原理,设$S_i$为$1$到$N$中和$a$有公因子$i$的数的个数,答案为$N−S_2-S_3-S_5-S_7...+S_{2,3}+S_{3,5}+S_{2,5}...$,可以发现,其答案为$\large \displaystyle \sum_{i=1}^{N}\mu(i)*S_i$。 + +> **注**:在使用容斥原理解决计数问题时,莫比乌斯函数的值就是每一项的系数,要么是$1$,要么是$-1$。这是因为莫比乌斯函数在容斥原理中的作用就是用来表示每个子集合的权重,从而在计算中起到排除重复计数的作用。 + + #### 3. 求单个数字的莫比乌斯函数值 ```cpp {.line-numbers} -//单个数的莫比乌斯函数 -int getmob(LL x) { +// 单个数的莫比乌斯函数 +int getmob(int x) { int sum = 0; - for (LL i = 2; i <= x / i; i++) { //从2开始,一直到 sqrt(x),枚举所有可能存的小因子 + for (int i = 2; i <= x / i; i++) { // 从2开始,一直到 sqrt(x),枚举所有可能存的小因子 int cnt = 0; - if (x % i == 0) { //如果x可以整除i - while (x % i == 0) cnt++, x /= i; //计数,并且不断除掉这个i因子 - if (cnt > 1) return 0; //如果某个因子,存在两个及以上个,则返回0 - sum++; //记录因子个数 + if (x % i == 0) { // 如果x可以整除i + while (x % i == 0) cnt++, x /= i; // 计数,并且不断除掉这个i因子 + if (cnt > 1) return 0; // 如果某个因子,存在两个及以上个,则返回0 + sum++; // 记录因子个数 } } - if (x != 1) sum++; //如果还存在另一个大因子,那么因子个数+1 - return (sum & 1) ? -1 : 1; //奇数个因子,返回-1,否则返回1 -} -``` - -#### 4.枚举倍数求莫比乌斯函数(埃氏筛) -```cpp {.line-numbers} -//枚举倍数求莫比乌斯函数 -LL mu[N] , sum[N]; -void mobius(LL x) { - mu[1] = 1; - for (LL i = 1; i <= x; i++) - for (LL j = i + i; j <= x; j += i) - mu[j] -= mu[i]; - // 维护u(x)前缀和 - for (LL i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i]; + if (x != 1) sum++; // 如果还存在另一个大因子,那么因子个数+1 + return (sum & 1) ? -1 : 1; // 奇数个因子,返回-1,否则返回1 } ``` - -#### 5.枚举约数求莫比乌斯函数(欧拉筛) +#### 4.欧拉筛扩展求莫比乌斯函数 **[视频讲解](https://www.bilibili.com/video/BV1Te4y1C7DP)** ```cpp {.line-numbers} -LL mu[N] , sum[N]; +// 筛法求莫比乌斯函数(枚举约数) +int mu[N], sum[N]; // sum[N]:梅滕斯函数,也就是莫比乌斯函数的前缀和 int primes[N], cnt; bool st[N]; -void get_mobius2(LL n) { +void get_mobius(int n) { mu[1] = 1; - for (LL i = 2; i <= n; i++) { + for (int i = 2; i <= n; i++) { if (!st[i]) { primes[cnt++] = i; mu[i] = -1; } - for (LL j = 0; primes[j] <= n / i; j++) { - LL t = primes[j] * i; + for (int j = 0; primes[j] <= n / i; j++) { + int t = primes[j] * i; st[t] = true; if (i % primes[j] == 0) { mu[t] = 0; break; } - mu[t] = mu[i] * -1; + mu[t] = -mu[i]; } } - // 维护u(x)前缀和 - for (LL i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i]; + // 维护u(x)前缀和:梅滕斯函数 + for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i]; } ``` -#### 6、性质$I$ -![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202312151706885.png) - -该图意为$n$的所有因子的莫比乌斯值的和只有在$n=1$时成立为$1$,其余通通为$0$; - -对这个性质的证明: - -首先我们可以把$n$分解为很多个质数相乘(此时质数的幂不一定为$1$): -令 $\large n=p_1^ {a_1}* p_2^ {a_2}* ... *p_k^{a_k}$ - -$\displaystyle \sum_{d | n} \mu(d)$ 这是求$n$的所有约数的莫比乌斯函数值的和,相当于在$p_1,p_2,p_3,...$这些素数中选择若干个质因子相乘来组成所有约数。 - -根据莫比乌斯函数性质,包含有素数平方的约数不用计算,他对答案的贡献值为$0$,所以我们可以把$n$分解的质数的次数全部消除为$1$,只有当莫比乌斯值为$-1$或者$1$时才对结果有贡献.那么问题就单纯的变为在$k$个$n$的质因子中选$0$到$k$个值组成约数,再将这些约数的值相加: -![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202312151710689.png) +### 二、题单 + **[$P4318$ 完全平方数](https://www.luogu.com.cn/problem/P4318)** -注意,这里的符号并不是全为加法,而是隔一个加隔一个减,这是因为莫比乌斯函数是积性函数,当选的数是奇数个时为值为负,反之为正,又因二项式定理,将$-1$和$1$带入,可以得到结果为$0$. +**题意** +筛去完全平方数及其的倍数,然后输出第$k$个的值。 - - -#### 7、性质$II$ -对任意正整数n -![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202312151712295.png) - -(ps:这条涉及莫比乌斯反演,还没学,学了再看) - -好了,这就是大致知识,我学完了这个还是不知道这东西到底有什么用,还是得靠实战: - -#### 题单 - **[完全平方数](https://www.luogu.com.cn/problem/P4318)** - - 题意就是筛去完全平方数及其的倍数,然后输出第k个的值. - -我们要求这个,就想到把1到ki的所有完全平方数和他的倍数筛去,但是一看数据,1e9,线性筛必定t,那再去想办法进行计算,我们先把2的平方4的倍数计算出来,在1到ki中,有ki/4个4的倍数,我们再计算的16的倍数个数时候,会发现在计算4的倍数个数时候已经把16的倍数个数计算过了,这里就重复了,而假设已经计算了4和9的倍数个数,再去计算36的倍数个数就会发现计算了两次,那么就要减去36的倍数个数,这里就已经想到可以用容斥了.这里我们发现这里需要枚举质数的平方的次数,且奇数偶数符号不相同,就会想到莫比乌斯函数.它计算枚举的边界是i*i<=n;我们再用n减去计算的出来的从2开始的到ki的完全平方数的个数即为所求: +我们要求这个,就想到把$1$到$k_i$的所有完全平方数和他的倍数筛去,但是一看数据,$1e9$,线性筛必定$T$,那再去想办法进行计算,我们先把$2$的平方$4$的倍数计算出来,在$1$到$k_i$中,有$k_i/4$个$4$的倍数,我们再计算的$16$的倍数个数时候,会发现在计算$4$的倍数个数时候已经把$16$的倍数个数计算过了,这里就重复了,而假设已经计算了$4$和$9$的倍数个数,再去计算$36$的倍数个数就会发现计算了两次,那么就要减去$36$的倍数个数,这里就已经想到可以用 **容斥** 了.这里我们发现这里 **需要枚举质数的平方的次数** ,且奇数偶数符号不相同,就会想到 **莫比乌斯函数**.它计算枚举的边界是$i*i<=n$;我们再用$n$减去计算的出来的从$2$开始的到$k_i$的完全平方数的个数即为所求: ![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202312151714471.png) @@ -214,7 +156,7 @@ int main() } ``` -例题:数字染色 +#### 例题:数字染色 题目意思: 给一个数组,在其中选数,看可以最多选出多少个gcd(最大公约数)>1的集合. @@ -292,6 +234,9 @@ int main() } ``` -ps:最后结果防止取模出负数要加上mod再取模. -之后会继续学习莫比乌斯反演的,如果本蒟蒻有什么错误望大佬指正. \ No newline at end of file +https://blog.csdn.net/qq_49593247/article/details/120394226 + +https://www.cnblogs.com/letlifestop/p/10262757.html + +// https://blog.csdn.net/qq_42887171/article/details/95237740 From e6ee6587ce6d505c9ef9487ad049530802c13c16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sat, 16 Dec 2023 12:29:11 +0800 Subject: [PATCH 03/25] 'commit' --- TangDou/Topic/莫比乌斯函数.md | 43 ++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/TangDou/Topic/莫比乌斯函数.md b/TangDou/Topic/莫比乌斯函数.md index dabd364..d187746 100644 --- a/TangDou/Topic/莫比乌斯函数.md +++ b/TangDou/Topic/莫比乌斯函数.md @@ -83,10 +83,51 @@ void get_mobius(int n) { ### 二、题单 +**[$SP4168$ $SQFREE$ - $Square$-$free$ $integers$](https://www.luogu.com.cn/problem/SP4168)** + +**题意** +在数论中,如果一个整数不能被任何一个整数(这个整数不是$1$)的平方整除,我们就称它是一个$Square−freeinteger$(**无平方数因数的数**)。你得数一数! + +**题解** + +考虑在$[1, n]$ 中,有多少个数是$x^2$的倍数? 显然是$⌊\frac{n}{x^2}⌋$个。 + +那 $⌊\frac{n}{x^2}⌋$ 就是答案了吗?当然不是,有大量的数被重复计算了。 + +例如 $36$,它等于$2^2\times 3^2$ ,在枚举 $2$ 和 $3$ 的时候它就会被重复计算。 + +怎么办呢?加加减减弄对呗! + +利用容斥的思想,$1$到$n$以内有平方因数的数有$\large \frac{n}{2^2}+\frac{n}{3^2}+\frac{n}{5^2}-\frac{n}{6^2}...$。 +> **注**:为什么没有$4^2$呢?这是因为$4^2=2^2 \times 2^2$,在计算$2^2$的个数时,已经计算过了,同理,合数都可以拆分成质数的乘积,我们只要计算质数就可以了。 +但是,$6^2=2^2\times 3^2$这样的数字比较特殊,它会被在计算$2^2$时计算一遍,在计算$3^2$时又计算了一遍,计算重复了,需要再扣除掉$\frac{n}{6^2}$个,这就是容斥原理。 + +**$Q$:即然这是容斥原理,那和莫比乌斯函数有什么关系呢?** +答:你从现实意义上去看看: +- $2,3,5$都是一个质数因子,它的系数是$1$ +- $6$有两个质数因子,系数就是$-1$, +这不就是莫比乌斯函数的定义吗? + +可知以上式子 $\displaystyle =-\sum_{2 \leq d \leq \sqrt{n}} \mu(d)*(n/d^2)$ +所以无平方因数的数就是求补集=$\displaystyle n-(-\sum_{2 \leq d \leq \sqrt{n}} \mu(d)*(n/d^2))=n+\sum_{2 \leq d \leq \sqrt{n}} \mu(d)*(n/d^2)$个。 + +变形合并,$d=1,\mu(d)=\mu(1)=1,n/d^2=n/1=n$,发现这个式子可以合并成$\displaystyle \sum_{1 \leq d \leq \sqrt{n}} \mu(d)*(n/d^2)$ + +> **注**: +> ① $d>\sqrt{n}$时,$⌊\frac{n}{d^2}⌋$恒等于$0$,只需要枚举到$\sqrt{n}$即可。 +> ② $⌊\frac{n}{d^2}⌋$是整除分块的基本形式,用整除分块优化,给$μ(x)$搞个前缀和。 + + **[$P4318$ 完全平方数](https://www.luogu.com.cn/problem/P4318)** **题意** -筛去完全平方数及其的倍数,然后输出第$k$个的值。 +小$X$自幼就很喜欢数。但奇怪的是,他十分讨厌完全平方数。他觉得这些数看起来很令人难受。由此,他也讨厌所有是完全平方数的正整数倍的数。然而这丝毫不影响他对其他数的热爱。 + +这天是小$X$的生日,小$W$想送一个数给他作为生日礼物。当然他不能送一个小$X$讨厌的数。他列出了所有小$X$不讨厌的数,然后选取了第$K$个数送给了小$X$。小$X$很开心地收下了。 + +然而现在小$W$却记不起送给小$X$的是哪个数了。你能帮他一下吗? + +**解法** 我们要求这个,就想到把$1$到$k_i$的所有完全平方数和他的倍数筛去,但是一看数据,$1e9$,线性筛必定$T$,那再去想办法进行计算,我们先把$2$的平方$4$的倍数计算出来,在$1$到$k_i$中,有$k_i/4$个$4$的倍数,我们再计算的$16$的倍数个数时候,会发现在计算$4$的倍数个数时候已经把$16$的倍数个数计算过了,这里就重复了,而假设已经计算了$4$和$9$的倍数个数,再去计算$36$的倍数个数就会发现计算了两次,那么就要减去$36$的倍数个数,这里就已经想到可以用 **容斥** 了.这里我们发现这里 **需要枚举质数的平方的次数** ,且奇数偶数符号不相同,就会想到 **莫比乌斯函数**.它计算枚举的边界是$i*i<=n$;我们再用$n$减去计算的出来的从$2$开始的到$k_i$的完全平方数的个数即为所求: From b20dc1a8bc219a37ccd02d43cba59917468ccb68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sat, 16 Dec 2023 12:34:57 +0800 Subject: [PATCH 04/25] 'commit' --- TangDou/Topic/莫比乌斯函数.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TangDou/Topic/莫比乌斯函数.md b/TangDou/Topic/莫比乌斯函数.md index d187746..e5ec7c7 100644 --- a/TangDou/Topic/莫比乌斯函数.md +++ b/TangDou/Topic/莫比乌斯函数.md @@ -113,9 +113,9 @@ void get_mobius(int n) { 变形合并,$d=1,\mu(d)=\mu(1)=1,n/d^2=n/1=n$,发现这个式子可以合并成$\displaystyle \sum_{1 \leq d \leq \sqrt{n}} \mu(d)*(n/d^2)$ +$⌊\frac{n}{d^2}⌋$是整除分块的基本形式,用整除分块优化,这样区间的数值都是一样的,但符号有加有减,一个个算太慢了,可以给$μ(x)$预处理出前缀和,这样直接用$O(1)$时间计算前缀和,再乘上整除分块的数值就行了~ > **注**: -> ① $d>\sqrt{n}$时,$⌊\frac{n}{d^2}⌋$恒等于$0$,只需要枚举到$\sqrt{n}$即可。 -> ② $⌊\frac{n}{d^2}⌋$是整除分块的基本形式,用整除分块优化,给$μ(x)$搞个前缀和。 +> $d>\sqrt{n}$时,$⌊\frac{n}{d^2}⌋$恒等于$0$,只需要枚举到$\sqrt{n}$即可。 **[$P4318$ 完全平方数](https://www.luogu.com.cn/problem/P4318)** From 2ccc942a7f390bfe3be9d6cd620aa420d624e0cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sat, 16 Dec 2023 12:55:15 +0800 Subject: [PATCH 05/25] 'commit' --- TangDou/Topic/Mobius/SQP4168.cpp | 62 ++++++++++++++++++++++++++++++++ TangDou/Topic/Mobius/SQP4168.in | 4 +++ 2 files changed, 66 insertions(+) create mode 100644 TangDou/Topic/Mobius/SQP4168.cpp create mode 100644 TangDou/Topic/Mobius/SQP4168.in diff --git a/TangDou/Topic/Mobius/SQP4168.cpp b/TangDou/Topic/Mobius/SQP4168.cpp new file mode 100644 index 0000000..720daa5 --- /dev/null +++ b/TangDou/Topic/Mobius/SQP4168.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +using namespace std; +#define int long long +#define endl "\n" + +const int N = 1e7 + 10; // 枚举到sqrt(1e14)=1e7即可 + +// 筛法求莫比乌斯函数(枚举约数) +int mu[N], s1[N], s2[N]; // s1[N]:梅滕斯函数,也就是莫比乌斯函数的前缀和 +int primes[N], cnt; +bool st[N]; +void get_mobius(int n) { + mu[1] = 1; + for (int i = 2; i <= n; i++) { + if (!st[i]) { + primes[cnt++] = i; + mu[i] = -1; + } + for (int j = 0; primes[j] <= n / i; j++) { + int t = primes[j] * i; + st[t] = true; + if (i % primes[j] == 0) { + mu[t] = 0; + break; + } + mu[t] = -mu[i]; + } + } + // 维护u(x)前缀和:梅滕斯函数 + for (int i = 1; i <= n; i++) s1[i] = s1[i - 1] + mu[i]; + for (int i = 1; i <= n; i++) s2[i] = s2[i - 1] + mu[i] * mu[i]; +} + +int calc(int n) { + if (n <= N) return s2[n]; + int res = 0, m = sqrt(n); + for (int l = 1, r; l <= m; l = r + 1) { + r = min((int)sqrt(n / (n / (l * l))), m); + res += (n / (l * l)) * (s1[r] - s1[l - 1]); + } + return res; +} + +signed main() { +#ifndef ONLINE_JUDGE + freopen("SQP4168.in", "r", stdin); + /* + 1 + 608 + 60792710185947 + */ +#endif + int n, T; + cin >> T; + get_mobius(N - 1); + while (T--) { + cin >> n; + cout << calc(n) << endl; + } +} diff --git a/TangDou/Topic/Mobius/SQP4168.in b/TangDou/Topic/Mobius/SQP4168.in new file mode 100644 index 0000000..6f696ea --- /dev/null +++ b/TangDou/Topic/Mobius/SQP4168.in @@ -0,0 +1,4 @@ +3 +1 +1000 +100000000000000 \ No newline at end of file From bc4ce40eb11b204f68be27581b85c3955347e6a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sat, 16 Dec 2023 13:06:27 +0800 Subject: [PATCH 06/25] 'commit' --- TangDou/Topic/Mobius/SQP4168.cpp | 54 +++++++++++++++----------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/TangDou/Topic/Mobius/SQP4168.cpp b/TangDou/Topic/Mobius/SQP4168.cpp index 720daa5..93c943a 100644 --- a/TangDou/Topic/Mobius/SQP4168.cpp +++ b/TangDou/Topic/Mobius/SQP4168.cpp @@ -1,14 +1,14 @@ -#include -#include -#include +#include using namespace std; #define int long long #define endl "\n" -const int N = 1e7 + 10; // 枚举到sqrt(1e14)=1e7即可 +const int M = 110, N = 10000010; + +int t, m, sqrtN, n, ans, q[M]; // 筛法求莫比乌斯函数(枚举约数) -int mu[N], s1[N], s2[N]; // s1[N]:梅滕斯函数,也就是莫比乌斯函数的前缀和 +int mu[N], sum[N]; // sum[N]:梅滕斯函数,也就是莫比乌斯函数的前缀和 int primes[N], cnt; bool st[N]; void get_mobius(int n) { @@ -29,34 +29,30 @@ void get_mobius(int n) { } } // 维护u(x)前缀和:梅滕斯函数 - for (int i = 1; i <= n; i++) s1[i] = s1[i - 1] + mu[i]; - for (int i = 1; i <= n; i++) s2[i] = s2[i - 1] + mu[i] * mu[i]; -} - -int calc(int n) { - if (n <= N) return s2[n]; - int res = 0, m = sqrt(n); - for (int l = 1, r; l <= m; l = r + 1) { - r = min((int)sqrt(n / (n / (l * l))), m); - res += (n / (l * l)) * (s1[r] - s1[l - 1]); - } - return res; + for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i]; } signed main() { #ifndef ONLINE_JUDGE freopen("SQP4168.in", "r", stdin); - /* - 1 - 608 - 60792710185947 - */ #endif - int n, T; - cin >> T; - get_mobius(N - 1); - while (T--) { - cin >> n; - cout << calc(n) << endl; + cin >> t; + for (int i = 1; i <= t; i++) { + cin >> q[i]; + n = max(n, q[i]); } -} + sqrtN = sqrt(n); + + get_mobius(sqrtN); // 线性求莫比乌斯函数, 前缀和 + + for (int i = 1; i <= t; i++) { + n = q[i]; + ans = 0; + for (int l = 1, r; l <= n; l = r + 1) { + if (n / (l * l) == 0) { break; } + r = sqrt(n / (n / (l * l))); + ans += n / (l * l) * (sum[r] - sum[l - 1]); + } + printf("%lld\n", ans); + } +} \ No newline at end of file From 246449b6565c9246712411c953ddbdb599db9de1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sat, 16 Dec 2023 13:18:34 +0800 Subject: [PATCH 07/25] 'commit' --- TangDou/Topic/Mobius/SQP4168.cpp | 37 +++++++++++++++++--------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/TangDou/Topic/Mobius/SQP4168.cpp b/TangDou/Topic/Mobius/SQP4168.cpp index 93c943a..e256858 100644 --- a/TangDou/Topic/Mobius/SQP4168.cpp +++ b/TangDou/Topic/Mobius/SQP4168.cpp @@ -2,10 +2,10 @@ using namespace std; #define int long long #define endl "\n" - -const int M = 110, N = 10000010; - -int t, m, sqrtN, n, ans, q[M]; +const int M = 110; // 询问次数 +const int N = 10000010; // 莫比乌斯函数值的极限数据上限,sqrt(1e14)=1e7 +int n, sqrtN; // T次询问,每次都是1~n,sqrtN=sqrt(max(n)),真实上限 +int q[M]; // T次询问,用q数组记录下来 // 筛法求莫比乌斯函数(枚举约数) int mu[N], sum[N]; // sum[N]:梅滕斯函数,也就是莫比乌斯函数的前缀和 @@ -36,23 +36,26 @@ signed main() { #ifndef ONLINE_JUDGE freopen("SQP4168.in", "r", stdin); #endif - cin >> t; - for (int i = 1; i <= t; i++) { + int T; + cin >> T; + for (int i = 1; i <= T; i++) { cin >> q[i]; - n = max(n, q[i]); + n = max(n, q[i]); // 找到最大的n,这样可以避免重复计算 } - sqrtN = sqrt(n); - + sqrtN = sqrt(n); // 最大的n,只需要枚举到sqrt(n)即可 + // 对有效范围内的数字求莫比乌斯函数 get_mobius(sqrtN); // 线性求莫比乌斯函数, 前缀和 - for (int i = 1; i <= t; i++) { - n = q[i]; - ans = 0; - for (int l = 1, r; l <= n; l = r + 1) { - if (n / (l * l) == 0) { break; } - r = sqrt(n / (n / (l * l))); - ans += n / (l * l) * (sum[r] - sum[l - 1]); + for (int i = 1; i <= T; i++) { // 离线处理,对于每个询问进行回答 + n = q[i]; // 第i次的n值 + int ans = 0; // 初始化返回结果 + for (int l = 1, r; l <= n; l = r + 1) { // 整除分块 + if (n / (l * l) == 0) break; + // n / (l * l): 分块的左边界是l,值是n/(l*l),如果n<(l*l)时,l再长大也没用,也都是0 + // n/(l*l):整除分块中整个分块内的个数值,从n/(l*l)~n/(r*r)是同一个值 + r = sqrt(n / (n / (l * l))); // 求出右边界r + ans += n / (l * l) * (sum[r] - sum[l - 1]); // 利用莫比乌斯函数值前缀和求块的贡献 } - printf("%lld\n", ans); + cout << ans << endl; } } \ No newline at end of file From 9be3b2170c4f2ab4c29aad93d30e1360ce036938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sat, 16 Dec 2023 13:19:01 +0800 Subject: [PATCH 08/25] 'commit' --- TangDou/Topic/莫比乌斯函数.md | 64 +++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/TangDou/Topic/莫比乌斯函数.md b/TangDou/Topic/莫比乌斯函数.md index e5ec7c7..689db96 100644 --- a/TangDou/Topic/莫比乌斯函数.md +++ b/TangDou/Topic/莫比乌斯函数.md @@ -117,6 +117,70 @@ $⌊\frac{n}{d^2}⌋$是整除分块的基本形式,用整除分块优化, > **注**: > $d>\sqrt{n}$时,$⌊\frac{n}{d^2}⌋$恒等于$0$,只需要枚举到$\sqrt{n}$即可。 +#### $Code$ +```cpp {.line-numbers} +#include +using namespace std; +#define int long long +#define endl "\n" +const int M = 110; // 询问次数 +const int N = 10000010; // 莫比乌斯函数值的极限数据上限,sqrt(1e14)=1e7 +int n, sqrtN; // T次询问,每次都是1~n,sqrtN=sqrt(max(n)),真实上限 +int q[M]; // T次询问,用q数组记录下来 + +// 筛法求莫比乌斯函数(枚举约数) +int mu[N], sum[N]; // sum[N]:梅滕斯函数,也就是莫比乌斯函数的前缀和 +int primes[N], cnt; +bool st[N]; +void get_mobius(int n) { + mu[1] = 1; + for (int i = 2; i <= n; i++) { + if (!st[i]) { + primes[cnt++] = i; + mu[i] = -1; + } + for (int j = 0; primes[j] <= n / i; j++) { + int t = primes[j] * i; + st[t] = true; + if (i % primes[j] == 0) { + mu[t] = 0; + break; + } + mu[t] = -mu[i]; + } + } + // 维护u(x)前缀和:梅滕斯函数 + for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i]; +} + +signed main() { +#ifndef ONLINE_JUDGE + freopen("SQP4168.in", "r", stdin); +#endif + int T; + cin >> T; + for (int i = 1; i <= T; i++) { + cin >> q[i]; + n = max(n, q[i]); // 找到最大的n,这样可以避免重复计算 + } + sqrtN = sqrt(n); // 最大的n,只需要枚举到sqrt(n)即可 + // 对有效范围内的数字求莫比乌斯函数 + get_mobius(sqrtN); // 线性求莫比乌斯函数, 前缀和 + + for (int i = 1; i <= T; i++) { // 离线处理,对于每个询问进行回答 + n = q[i]; // 第i次的n值 + int ans = 0; // 初始化返回结果 + for (int l = 1, r; l <= n; l = r + 1) { // 整除分块 + if (n / (l * l) == 0) break; + // n / (l * l): 分块的左边界是l,值是n/(l*l),如果n<(l*l)时,l再长大也没用,也都是0 + // n/(l*l):整除分块中整个分块内的个数值,从n/(l*l)~n/(r*r)是同一个值 + r = sqrt(n / (n / (l * l))); // 求出右边界r + ans += n / (l * l) * (sum[r] - sum[l - 1]); // 利用莫比乌斯函数值前缀和求块的贡献 + } + cout << ans << endl; + } +} +``` **[$P4318$ 完全平方数](https://www.luogu.com.cn/problem/P4318)** From 45fd2a5fef5450cf68a1e58b451e071a6dd5c155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sat, 16 Dec 2023 14:20:14 +0800 Subject: [PATCH 09/25] 'commit' --- TangDou/Topic/莫比乌斯函数.md | 40 ++++++++++++++++++----------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/TangDou/Topic/莫比乌斯函数.md b/TangDou/Topic/莫比乌斯函数.md index 689db96..2c5d1e9 100644 --- a/TangDou/Topic/莫比乌斯函数.md +++ b/TangDou/Topic/莫比乌斯函数.md @@ -90,32 +90,42 @@ void get_mobius(int n) { **题解** -考虑在$[1, n]$ 中,有多少个数是$x^2$的倍数? 显然是$⌊\frac{n}{x^2}⌋$个。 +先来求一下 **平方数因数** 的数有多少个: + +在$[1, n]$ 中,有多少个数是$x^2$的倍数? 显然是$⌊\frac{n}{x^2}⌋$个。 那 $⌊\frac{n}{x^2}⌋$ 就是答案了吗?当然不是,有大量的数被重复计算了。 -例如 $36$,它等于$2^2\times 3^2$ ,在枚举 $2$ 和 $3$ 的时候它就会被重复计算。 +例如 $36$,它等于$2^2\times 3^2$ ,在计算 $2$ 和 $3$ 的时候它会被重复计算。 -怎么办呢?加加减减弄对呗! +怎么办呢? -利用容斥的思想,$1$到$n$以内有平方因数的数有$\large \frac{n}{2^2}+\frac{n}{3^2}+\frac{n}{5^2}-\frac{n}{6^2}...$。 -> **注**:为什么没有$4^2$呢?这是因为$4^2=2^2 \times 2^2$,在计算$2^2$的个数时,已经计算过了,同理,合数都可以拆分成质数的乘积,我们只要计算质数就可以了。 -但是,$6^2=2^2\times 3^2$这样的数字比较特殊,它会被在计算$2^2$时计算一遍,在计算$3^2$时又计算了一遍,计算重复了,需要再扣除掉$\frac{n}{6^2}$个,这就是容斥原理。 +利用容斥思想,$1$到$n$以内有平方因数的数有$\large \frac{n}{2^2}+\frac{n}{3^2}+\frac{n}{5^2}-\frac{n}{6^2}...$。 +> **注**: +> ① 为什么没有$4^2,8^2$呢?这是因为$4^2=4 \times 2^2,8^2=16\times 2^2$,在计算$2^2$的个数时,已经计算过了,同理,合数都可以拆分成质数的乘积,我们只要计算质数的平方就可以了。 +② 但是 $6^2=2^2\times 3^2$这样的数字比较特殊,它会被在计算$2^2$时计算一遍,在计算$3^2$时又计算了一遍,计算重复了,需要再扣除掉$\frac{n}{6^2}$个,这里可以看出来符合容斥原理。 -**$Q$:即然这是容斥原理,那和莫比乌斯函数有什么关系呢?** -答:你从现实意义上去看看: +**$Q$:即然这是容斥原理,和莫比乌斯函数有什么关系?** +答:从现实意义上去看看: - $2,3,5$都是一个质数因子,它的系数是$1$ -- $6$有两个质数因子,系数就是$-1$, +- $4=2^2$,按莫比乌斯函数的说法,系数为$0$,无贡献 +- $6 = 2 \times 3$,有两个质数因子,系数是$-1$ + 这不就是莫比乌斯函数的定义吗? -可知以上式子 $\displaystyle =-\sum_{2 \leq d \leq \sqrt{n}} \mu(d)*(n/d^2)$ -所以无平方因数的数就是求补集=$\displaystyle n-(-\sum_{2 \leq d \leq \sqrt{n}} \mu(d)*(n/d^2))=n+\sum_{2 \leq d \leq \sqrt{n}} \mu(d)*(n/d^2)$个。 +可知以上式子 $\displaystyle =-\sum_{2 \leq d \leq \sqrt{n}} \mu(d)*⌊\frac{n}{d^2}⌋$ +> **注**:为什么只枚举到$\sqrt{n}$,而不是到$n$呢?这是因为:$d>\sqrt{n}$时,$⌊\frac{n}{d^2}⌋$恒等于$0$,再继续也是无贡献,只需要枚举到$\sqrt{n}$即可。 -变形合并,$d=1,\mu(d)=\mu(1)=1,n/d^2=n/1=n$,发现这个式子可以合并成$\displaystyle \sum_{1 \leq d \leq \sqrt{n}} \mu(d)*(n/d^2)$ +所以无平方因数的数就是 **求补集**=$\displaystyle n-(-\sum_{2 \leq d \leq \sqrt{n}} \mu(d)*⌊\frac{n}{d^2}⌋)=n+\sum_{2 \leq d \leq \sqrt{n}} \mu(d)*⌊\frac{n}{d^2}⌋$个。 + +变形合并,$d=1,\mu(d)=1$(莫比乌斯的分段函数求得) +$\mu(d)*⌊\frac{n}{d^2}⌋=\mu(1)*(n/1^2)=1*n=n$ + +此时可以发现这个式子,第一项也可以合并进来,合并成 +$$\displaystyle \sum_{1 \leq d \leq \sqrt{n}} \mu(d)*⌊\frac{n}{d^2}⌋$$ + +$⌊\frac{n}{d^2}⌋$是整除分块的基本形式,用整除分块优化,区间的数值都是一样的,但每项的符号有加有减,一个个算太慢了,可以给$μ(x)$预处理出前缀和,这样直接用$O(1)$时间计算前缀和,再乘上整除分块的数值就行了~ -$⌊\frac{n}{d^2}⌋$是整除分块的基本形式,用整除分块优化,这样区间的数值都是一样的,但符号有加有减,一个个算太慢了,可以给$μ(x)$预处理出前缀和,这样直接用$O(1)$时间计算前缀和,再乘上整除分块的数值就行了~ -> **注**: -> $d>\sqrt{n}$时,$⌊\frac{n}{d^2}⌋$恒等于$0$,只需要枚举到$\sqrt{n}$即可。 #### $Code$ ```cpp {.line-numbers} From 840774332d1bb3abd999bd1b4a2416c0955a706c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sat, 16 Dec 2023 14:38:03 +0800 Subject: [PATCH 10/25] 'commit' --- TangDou/Topic/Mobius/P4318.cpp | 42 +++++++++++++++++++++++++++++ TangDou/Topic/莫比乌斯函数.md | 10 +++---- 2 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 TangDou/Topic/Mobius/P4318.cpp diff --git a/TangDou/Topic/Mobius/P4318.cpp b/TangDou/Topic/Mobius/P4318.cpp new file mode 100644 index 0000000..d25ed33 --- /dev/null +++ b/TangDou/Topic/Mobius/P4318.cpp @@ -0,0 +1,42 @@ +#include +using namespace std; +int t, k, z[100005], p[100005], mu[100005]; +long long check(long long g) { + long long res = 0; + for (int i = 1; i * i <= g; i++) + res = res + mu[i] * (g / i / i); + + return res; +} +int main() { + mu[1] = 1; + for (int i = 2; i <= 100000; i++) { + if (!z[i]) { + p[++p[0]] = i; + mu[i] = -1; + } + for (int j = 1; j <= p[0] && i * p[j] <= 100000; j++) { + z[i * p[j]] = 1; + if (i % p[j] == 0) { + mu[i * p[j]] = 0; + break; + } + mu[i * p[j]] = -mu[i]; + } + } + cin >> t; + while (t--) { + cin >> k; + long long l = 1, r = 2e9, mid, vt; + while (l <= r) { + mid = (l + r) / 2; + vt = check(mid); + if (vt < k) + l = mid + 1; + else + r = mid - 1; + } + cout << r + 1 << endl; + } + return 0; +} diff --git a/TangDou/Topic/莫比乌斯函数.md b/TangDou/Topic/莫比乌斯函数.md index 2c5d1e9..43ece3c 100644 --- a/TangDou/Topic/莫比乌斯函数.md +++ b/TangDou/Topic/莫比乌斯函数.md @@ -88,6 +88,8 @@ void get_mobius(int n) { **题意** 在数论中,如果一个整数不能被任何一个整数(这个整数不是$1$)的平方整除,我们就称它是一个$Square−freeinteger$(**无平方数因数的数**)。你得数一数! +求出$1 \sim n$ 中无平方因子数的个数。 + **题解** 先来求一下 **平方数因数** 的数有多少个: @@ -202,12 +204,8 @@ signed main() { 然而现在小$W$却记不起送给小$X$的是哪个数了。你能帮他一下吗? **解法** - -我们要求这个,就想到把$1$到$k_i$的所有完全平方数和他的倍数筛去,但是一看数据,$1e9$,线性筛必定$T$,那再去想办法进行计算,我们先把$2$的平方$4$的倍数计算出来,在$1$到$k_i$中,有$k_i/4$个$4$的倍数,我们再计算的$16$的倍数个数时候,会发现在计算$4$的倍数个数时候已经把$16$的倍数个数计算过了,这里就重复了,而假设已经计算了$4$和$9$的倍数个数,再去计算$36$的倍数个数就会发现计算了两次,那么就要减去$36$的倍数个数,这里就已经想到可以用 **容斥** 了.这里我们发现这里 **需要枚举质数的平方的次数** ,且奇数偶数符号不相同,就会想到 **莫比乌斯函数**.它计算枚举的边界是$i*i<=n$;我们再用$n$减去计算的出来的从$2$开始的到$k_i$的完全平方数的个数即为所求: - -![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202312151714471.png) - -当求出来之后,我们就可以用二分来求此时的值: +设$f(n)$表示在$1$到$n$中小$X$不讨厌的数的数量。显然$f(n)$是 **单调递增** 的,所以我们可以二分答案。 +> **注**: 与上一题的区别在于上一题明确给出了最大值$n$,也就是右边界的范围,本题没有告诉我们范围,需要我们自己找到右边界。随着右边界越来越大,肯定符合条件的数字个数也会越来越多,也就是上面说到的单调性。我们可以用二分来假设一个右边界,然后不断的收缩区间来找到准备的右边界:在上道题的基础上加上二分,判断$1$到$mid$是否有$K$个无平方因子的数,以此改变左右边界即可。 ```cpp {.line-numbers} #include From 8b53f3aad50ab6cb71e0428709cb705b835ec6d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sat, 16 Dec 2023 14:40:37 +0800 Subject: [PATCH 11/25] 'commit' --- TangDou/Topic/Mobius/P4318.cpp | 46 ++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/TangDou/Topic/Mobius/P4318.cpp b/TangDou/Topic/Mobius/P4318.cpp index d25ed33..073318c 100644 --- a/TangDou/Topic/Mobius/P4318.cpp +++ b/TangDou/Topic/Mobius/P4318.cpp @@ -1,29 +1,43 @@ #include using namespace std; -int t, k, z[100005], p[100005], mu[100005]; -long long check(long long g) { - long long res = 0; - for (int i = 1; i * i <= g; i++) - res = res + mu[i] * (g / i / i); +const int N = 100010; - return res; -} -int main() { +// 筛法求莫比乌斯函数(枚举约数) +int mu[N], sum[N]; // sum[N]:梅滕斯函数,也就是莫比乌斯函数的前缀和 +int primes[N], cnt; +bool st[N]; +void get_mobius(int n) { mu[1] = 1; - for (int i = 2; i <= 100000; i++) { - if (!z[i]) { - p[++p[0]] = i; + for (int i = 2; i <= n; i++) { + if (!st[i]) { + primes[cnt++] = i; mu[i] = -1; } - for (int j = 1; j <= p[0] && i * p[j] <= 100000; j++) { - z[i * p[j]] = 1; - if (i % p[j] == 0) { - mu[i * p[j]] = 0; + for (int j = 0; primes[j] <= n / i; j++) { + int t = primes[j] * i; + st[t] = true; + if (i % primes[j] == 0) { + mu[t] = 0; break; } - mu[i * p[j]] = -mu[i]; + mu[t] = -mu[i]; } } + // 维护u(x)前缀和:梅滕斯函数 + for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i]; +} +long long check(long long g) { + long long res = 0; + for (int i = 1; i * i <= g; i++) + res = res + mu[i] * (g / i / i); + + return res; +} + +int main() { + get_mobius(N - 1); + + int t, k; cin >> t; while (t--) { cin >> k; From f3f7465f3eb8bbfe07abc721dfa8f39c65a939bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sat, 16 Dec 2023 14:41:34 +0800 Subject: [PATCH 12/25] 'commit' --- TangDou/Topic/Mobius/P4318.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/TangDou/Topic/Mobius/P4318.cpp b/TangDou/Topic/Mobius/P4318.cpp index 073318c..8fa3082 100644 --- a/TangDou/Topic/Mobius/P4318.cpp +++ b/TangDou/Topic/Mobius/P4318.cpp @@ -1,5 +1,7 @@ #include using namespace std; +#define int long long +#define endl "\n" const int N = 100010; // 筛法求莫比乌斯函数(枚举约数) @@ -26,22 +28,22 @@ void get_mobius(int n) { // 维护u(x)前缀和:梅滕斯函数 for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i]; } -long long check(long long g) { - long long res = 0; +int check(int g) { + int res = 0; for (int i = 1; i * i <= g; i++) res = res + mu[i] * (g / i / i); return res; } -int main() { +signed main() { get_mobius(N - 1); int t, k; cin >> t; while (t--) { cin >> k; - long long l = 1, r = 2e9, mid, vt; + int l = 1, r = 2e9, mid, vt; while (l <= r) { mid = (l + r) / 2; vt = check(mid); @@ -52,5 +54,4 @@ int main() { } cout << r + 1 << endl; } - return 0; } From 6652870f6ba4f3d8ee746e65969d523e4ecf97b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sat, 16 Dec 2023 14:50:51 +0800 Subject: [PATCH 13/25] 'commit' --- TangDou/Topic/Mobius/P4318.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/TangDou/Topic/Mobius/P4318.cpp b/TangDou/Topic/Mobius/P4318.cpp index 8fa3082..61ddbfc 100644 --- a/TangDou/Topic/Mobius/P4318.cpp +++ b/TangDou/Topic/Mobius/P4318.cpp @@ -28,26 +28,26 @@ void get_mobius(int n) { // 维护u(x)前缀和:梅滕斯函数 for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i]; } -int check(int g) { +int check(int g, int k) { int res = 0; for (int i = 1; i * i <= g; i++) res = res + mu[i] * (g / i / i); - return res; + return res >= k; } signed main() { + // 获取莫比乌斯函数值 get_mobius(N - 1); - int t, k; - cin >> t; - while (t--) { - cin >> k; - int l = 1, r = 2e9, mid, vt; + int T, k; + cin >> T; // T组测试数据 + while (T--) { + cin >> k; // 第k个数 + int l = 1, r = 2e9; while (l <= r) { - mid = (l + r) / 2; - vt = check(mid); - if (vt < k) + int mid = l + r >> 1; + if (!check(mid, k)) l = mid + 1; else r = mid - 1; From c1e6795d43c97705c64622e48c636771bb6bff21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sat, 16 Dec 2023 14:55:19 +0800 Subject: [PATCH 14/25] 'commit' --- TangDou/Topic/Mobius/P4318.cpp | 21 +++---- TangDou/Topic/莫比乌斯函数.md | 93 ++++++++++++++--------------- 2 files changed, 53 insertions(+), 61 deletions(-) diff --git a/TangDou/Topic/Mobius/P4318.cpp b/TangDou/Topic/Mobius/P4318.cpp index 61ddbfc..753b170 100644 --- a/TangDou/Topic/Mobius/P4318.cpp +++ b/TangDou/Topic/Mobius/P4318.cpp @@ -5,7 +5,7 @@ using namespace std; const int N = 100010; // 筛法求莫比乌斯函数(枚举约数) -int mu[N], sum[N]; // sum[N]:梅滕斯函数,也就是莫比乌斯函数的前缀和 +int mu[N]; int primes[N], cnt; bool st[N]; void get_mobius(int n) { @@ -25,14 +25,11 @@ void get_mobius(int n) { mu[t] = -mu[i]; } } - // 维护u(x)前缀和:梅滕斯函数 - for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i]; } -int check(int g, int k) { +int check(int mid, int k) { int res = 0; - for (int i = 1; i * i <= g; i++) - res = res + mu[i] * (g / i / i); - + for (int i = 1; i * i <= mid; i++) // 枚举范围内每个平方数 + res += mu[i] * (mid / i / i); return res >= k; } @@ -45,13 +42,13 @@ signed main() { while (T--) { cin >> k; // 第k个数 int l = 1, r = 2e9; - while (l <= r) { + while (l < r) { int mid = l + r >> 1; - if (!check(mid, k)) - l = mid + 1; + if (check(mid, k)) + r = mid; else - r = mid - 1; + l = mid + 1; } - cout << r + 1 << endl; + cout << r << endl; } } diff --git a/TangDou/Topic/莫比乌斯函数.md b/TangDou/Topic/莫比乌斯函数.md index 43ece3c..4cb1bd4 100644 --- a/TangDou/Topic/莫比乌斯函数.md +++ b/TangDou/Topic/莫比乌斯函数.md @@ -208,64 +208,59 @@ signed main() { > **注**: 与上一题的区别在于上一题明确给出了最大值$n$,也就是右边界的范围,本题没有告诉我们范围,需要我们自己找到右边界。随着右边界越来越大,肯定符合条件的数字个数也会越来越多,也就是上面说到的单调性。我们可以用二分来假设一个右边界,然后不断的收缩区间来找到准备的右边界:在上道题的基础上加上二分,判断$1$到$mid$是否有$K$个无平方因子的数,以此改变左右边界即可。 ```cpp {.line-numbers} -#include -#define ll long long +#include using namespace std; -int vis[40560],mo[40560],p[4253],n; -void init() -{ - int tot=0,k; - mo[1]=1; - for(int i=2;i<=40559;i++) - { - if(vis[i]==0) - { - p[++tot]=i; - mo[i]=-1; +#define int long long +#define endl "\n" +const int N = 100010; + +// 筛法求莫比乌斯函数(枚举约数) +int mu[N]; +int primes[N], cnt; +bool st[N]; +void get_mobius(int n) { + mu[1] = 1; + for (int i = 2; i <= n; i++) { + if (!st[i]) { + primes[cnt++] = i; + mu[i] = -1; } - for(int j=1;j<=tot&&(k=i*p[j])<=40559;j++) - { - vis[k]=1; - if(i%p[j]!=0)mo[k]=-mo[i]; - else - { - mo[k]=0; + for (int j = 0; primes[j] <= n / i; j++) { + int t = primes[j] * i; + st[t] = true; + if (i % primes[j] == 0) { + mu[t] = 0; break; } + mu[t] = -mu[i]; } } } -bool judge(int x) -{ - ll ans=0;int i; - for(int i=1;i*i<=x;i++) - { - ans+=mo[i]*(x/(i*i)); - } - if(ans>=n)return true; - else return false; +int check(int mid, int k) { + int res = 0; + for (int i = 1; i * i <= mid; i++) // 枚举范围内每个平方数 + res += mu[i] * (mid / i / i); + return res >= k; } -int main() -{ - int t; - ll l,r,mid; - init(); - scanf("%d",&t); - while(t--) - { - scanf("%d",&n); - l=n,r=1644934082; - while(l>1; - if(judge(mid)) - r=mid; - else l=mid+1; - } - printf("%lld\n",r); + +signed main() { + // 获取莫比乌斯函数值 + get_mobius(N - 1); + + int T, k; + cin >> T; // T组测试数据 + while (T--) { + cin >> k; // 第k个数 + int l = 1, r = 2e9; + while (l < r) { + int mid = l + r >> 1; + if (check(mid, k)) + r = mid; + else + l = mid + 1; + } + cout << r << endl; } - return 0; } ``` From 382b37c11efb606836312d72aaaca6f557265752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sun, 17 Dec 2023 09:03:28 +0800 Subject: [PATCH 15/25] 'commit' --- .../Topic/{ => Mobius}/莫比乌斯函数.cpp | 0 TangDou/Topic/PrefixAndSuffix/P1115.cpp | 21 ++++ TangDou/Topic/PrefixAndSuffix/P3397.cpp | 28 +++++ .../前缀和和差分洛谷题单总结.md | 103 ++++++++++++++++++ 4 files changed, 152 insertions(+) rename TangDou/Topic/{ => Mobius}/莫比乌斯函数.cpp (100%) create mode 100644 TangDou/Topic/PrefixAndSuffix/P1115.cpp create mode 100644 TangDou/Topic/PrefixAndSuffix/P3397.cpp create mode 100644 TangDou/Topic/前缀和和差分洛谷题单总结.md diff --git a/TangDou/Topic/莫比乌斯函数.cpp b/TangDou/Topic/Mobius/莫比乌斯函数.cpp similarity index 100% rename from TangDou/Topic/莫比乌斯函数.cpp rename to TangDou/Topic/Mobius/莫比乌斯函数.cpp diff --git a/TangDou/Topic/PrefixAndSuffix/P1115.cpp b/TangDou/Topic/PrefixAndSuffix/P1115.cpp new file mode 100644 index 0000000..2805f03 --- /dev/null +++ b/TangDou/Topic/PrefixAndSuffix/P1115.cpp @@ -0,0 +1,21 @@ +#include +using namespace std; +const int N = 1e6 + 10; +const int INF = 0x3f3f3f3f; +int n, a[N], s[N], ans[N]; + +int main() { + cin >> n; + for (int i = 1; i <= n; i++) + cin >> a[i], s[i] = s[i - 1] + a[i]; // 前缀和 + + int mi = 0; + for (int i = 1; i <= n; i++) { + ans[i] = s[i] - mi; + mi = min(mi, s[i]); + } + int res = -INF; + for (int i = 1; i <= n; i++) res = max(res, ans[i]); + cout << res << endl; + return 0; +} \ No newline at end of file diff --git a/TangDou/Topic/PrefixAndSuffix/P3397.cpp b/TangDou/Topic/PrefixAndSuffix/P3397.cpp new file mode 100644 index 0000000..f4717c9 --- /dev/null +++ b/TangDou/Topic/PrefixAndSuffix/P3397.cpp @@ -0,0 +1,28 @@ +#include +using namespace std; + +const int N = 1010; +int b[N][N], s[N][N]; +int n, m; + +int main() { + cin >> n >> m; + while (m--) { + // 从0开始构建差分数组 + int x1, y1, x2, y2; + cin >> x1 >> y1 >> x2 >> y2; + b[x1][y1] += 1; // 进行子矩阵的加减,差分 + b[x2 + 1][y1] -= 1; + b[x1][y2 + 1] -= 1; + b[x2 + 1][y2 + 1] += 1; + } + // 还原为原始数组 + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= n; j++) { + s[i][j] = b[i][j] + s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1]; // 把之前的加减结果进行求和 + printf("%d ", s[i][j]); // 注意输出格式,每个数带一个空格 + } + printf("\n"); // 结束一行的输出输出一个换行符号 + } + return 0; +} \ No newline at end of file diff --git a/TangDou/Topic/前缀和和差分洛谷题单总结.md b/TangDou/Topic/前缀和和差分洛谷题单总结.md new file mode 100644 index 0000000..85da93f --- /dev/null +++ b/TangDou/Topic/前缀和和差分洛谷题单总结.md @@ -0,0 +1,103 @@ +## 前缀和和差分洛谷题单总结 + +[参考文献](https://blog.csdn.net/piqihaoshaonian/article/details/127515000) + +### 一、公式 +#### 前缀和的公式 +一维:$s[i] = a[i] + s[i-1]$ + +二维:$s[i][j] = a[i][j] + s[i-1] [j] + s[ i] [j-1] - s[i-1][j-1]$ + +#### 差分的公式 +一维:$b[i] =s[i] - s[i-1]$ + +二维:$b[i] = s[i][j] - s[i-1][j]-s[i][j-1]+s[i-1][j-1]$ + +### 二、题单 +#### [$P1115$ 最大子段和](https://www.luogu.com.cn/problem/P1115) +![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202312170827397.png) + +**分析** +先求每个位置的前缀和(某个区间求和前缀和可以说是最快的),然后去找该位置前前缀和的最小值,如果要求一段和最大,就要用这段和减去前面最小的值。 + +```cpp {.line-numbers} +#include +using namespace std; +const int N = 1e6 + 10; +const int INF = 0x3f3f3f3f; +int n, a[N], s[N], ans[N]; + +int main() { + cin >> n; + for (int i = 1; i <= n; i++) + cin >> a[i], s[i] = s[i - 1] + a[i]; // 前缀和 + + int mi = 0; + for (int i = 1; i <= n; i++) { + ans[i] = s[i] - mi; + mi = min(mi, s[i]); + } + int res = -INF; + for (int i = 1; i <= n; i++) res = max(res, ans[i]); + cout << res << endl; + return 0; +} +``` + +#### [$P3397$ 地毯](https://www.luogu.com.cn/problem/P3397) +![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202312170847131.png) + +**分析** +看到这里的时候,我就想到了一个矩阵的某个子矩阵进行加减,瞬间想到二维差分和二位前缀和,二位差分的公式为: + +![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202312170848375.png) + +由差分算的二位前缀和公式: +![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202312170849629.png) + +```cpp {.line-numbers} +#include +using namespace std; + +const int N = 1010; +int b[N][N], s[N][N]; +int n, m; + +int main() { + cin >> n >> m; + while (m--) { + // 从0开始构建差分数组 + int x1, y1, x2, y2; + cin >> x1 >> y1 >> x2 >> y2; + b[x1][y1] += 1; // 进行子矩阵的加减,差分 + b[x2 + 1][y1] -= 1; + b[x1][y2 + 1] -= 1; + b[x2 + 1][y2 + 1] += 1; + } + // 还原为原始数组 + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= n; j++) { + s[i][j] = b[i][j] + s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1]; // 把之前的加减结果进行求和 + printf("%d ", s[i][j]); // 注意输出格式,每个数带一个空格 + } + printf("\n"); // 结束一行的输出输出一个换行符号 + } + return 0; +} +``` + +### [$P3406$ 海底高铁](https://www.luogu.com.cn/problem/P3406) +![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202312170857701.png) + +**分析** +① 每一段的最小费用加起来则总体费用最小。 + +② 这里的区间是线段而不是一个具体的数,所以我们需要以一个统一的标准进行区间段的区分:于是我们想到了以每个区间的左端点值进行整个线段的记录。 + +③ 节约时间可以对某段区间做同样的加减数的方法:想到的就是差分(当然有差分就会有由差分求前缀和)。 + +④ 最后用得到的线段数比较两种购买方案。 + + +**注意** +当然代码中还有很多需要记录的细节!!!例如:线段数是站点数-1,同时差分和前缀和的循环最好是从1开始(涉及边界问题) \ No newline at end of file From 741b5ba20eb657946e31991fbddc2831fd045b25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sun, 17 Dec 2023 09:04:56 +0800 Subject: [PATCH 16/25] 'commit' --- TangDou/Topic/PrefixAndSuffix/P3406.cpp | 33 +++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 TangDou/Topic/PrefixAndSuffix/P3406.cpp diff --git a/TangDou/Topic/PrefixAndSuffix/P3406.cpp b/TangDou/Topic/PrefixAndSuffix/P3406.cpp new file mode 100644 index 0000000..4376f2e --- /dev/null +++ b/TangDou/Topic/PrefixAndSuffix/P3406.cpp @@ -0,0 +1,33 @@ +#include +using namespace std; +#define int long long +#define endl "\n" + +signed main() { + int n, m, ans = 0; + scanf("%lld%lld", &n, &m); + int p[m + 1]; // 用于记录经过站点的顺序 + int t[n + 1] = {}; // 用于记录站点之间的路径经过的次数 + int a[n + 1], b[n + 1], c[n + 1], x, y; // 用于记录每段路径所花的费用 + for (int i = 1; i <= m; i++) + scanf("%lld", &p[i]); + for (int i = 1; i <= n - 1; i++) + scanf("%lld%lld%lld", &a[i], &b[i], &c[i]); + for (int i = 1; i <= m - 1; i++) { // 所有的区间都以较小的点排在前面,例如:2-1,5-3都用1-2,3-5表示,且每一段都用前面较小的点作为标记!!!! + if (p[i] > p[i + 1]) { + x = p[i + 1]; + y = p[i]; + } else { + x = p[i]; + y = p[i + 1]; + } + t[x]++; + t[y]--; + } + for (int i = 1; i <= n; i++) { // 求前缀和 + t[i] += t[i - 1]; + } + for (int i = 1; i <= n - 1; i++) + ans += min(a[i] * t[i], (b[i] * t[i] + c[i])); // 求总的最小就是把每一段的最小相加 + printf("%lld", ans); +} \ No newline at end of file From 7d1c562ee04b7f55bd0c7f092e4394c1368cb655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sun, 17 Dec 2023 09:06:28 +0800 Subject: [PATCH 17/25] 'commit' --- TangDou/Topic/PrefixAndSuffix/P3406.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/TangDou/Topic/PrefixAndSuffix/P3406.cpp b/TangDou/Topic/PrefixAndSuffix/P3406.cpp index 4376f2e..23ccdfb 100644 --- a/TangDou/Topic/PrefixAndSuffix/P3406.cpp +++ b/TangDou/Topic/PrefixAndSuffix/P3406.cpp @@ -2,13 +2,14 @@ using namespace std; #define int long long #define endl "\n" - +const int N = 100010; +int n, m, ans; +int p[N]; // 用于记录经过站点的顺序 +int t[N]; // 用于记录站点之间的路径经过的次数 +int a[N], b[N], c[N], x, y; // 用于记录每段路径所花的费用 signed main() { - int n, m, ans = 0; - scanf("%lld%lld", &n, &m); - int p[m + 1]; // 用于记录经过站点的顺序 - int t[n + 1] = {}; // 用于记录站点之间的路径经过的次数 - int a[n + 1], b[n + 1], c[n + 1], x, y; // 用于记录每段路径所花的费用 + cin >> n >> m; + for (int i = 1; i <= m; i++) scanf("%lld", &p[i]); for (int i = 1; i <= n - 1; i++) From b81a64629c5afeec356947745e5f85623cfdecfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sun, 17 Dec 2023 09:07:35 +0800 Subject: [PATCH 18/25] 'commit' --- TangDou/Topic/PrefixAndSuffix/P3406.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/TangDou/Topic/PrefixAndSuffix/P3406.cpp b/TangDou/Topic/PrefixAndSuffix/P3406.cpp index 23ccdfb..26a6f57 100644 --- a/TangDou/Topic/PrefixAndSuffix/P3406.cpp +++ b/TangDou/Topic/PrefixAndSuffix/P3406.cpp @@ -10,11 +10,10 @@ int a[N], b[N], c[N], x, y; // 用于记录每段路径所花的费用 signed main() { cin >> n >> m; - for (int i = 1; i <= m; i++) - scanf("%lld", &p[i]); - for (int i = 1; i <= n - 1; i++) - scanf("%lld%lld%lld", &a[i], &b[i], &c[i]); - for (int i = 1; i <= m - 1; i++) { // 所有的区间都以较小的点排在前面,例如:2-1,5-3都用1-2,3-5表示,且每一段都用前面较小的点作为标记!!!! + for (int i = 1; i <= m; i++) cin >> p[i]; + for (int i = 1; i < n; i++) cin >> a[i] >> b[i] >> c[i]; + + for (int i = 1; i < m; i++) { // 所有的区间都以较小的点排在前面,例如:2-1,5-3都用1-2,3-5表示,且每一段都用前面较小的点作为标记!!!! if (p[i] > p[i + 1]) { x = p[i + 1]; y = p[i]; @@ -30,5 +29,5 @@ signed main() { } for (int i = 1; i <= n - 1; i++) ans += min(a[i] * t[i], (b[i] * t[i] + c[i])); // 求总的最小就是把每一段的最小相加 - printf("%lld", ans); + cout << ans << endl; } \ No newline at end of file From 8c319e26496706589d8d4d373fd53c0bf192b1e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sun, 17 Dec 2023 09:12:56 +0800 Subject: [PATCH 19/25] 'commit' --- TangDou/Topic/PrefixAndSuffix/P3406.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/TangDou/Topic/PrefixAndSuffix/P3406.cpp b/TangDou/Topic/PrefixAndSuffix/P3406.cpp index 26a6f57..b5ca5d1 100644 --- a/TangDou/Topic/PrefixAndSuffix/P3406.cpp +++ b/TangDou/Topic/PrefixAndSuffix/P3406.cpp @@ -3,17 +3,21 @@ using namespace std; #define int long long #define endl "\n" const int N = 100010; -int n, m, ans; -int p[N]; // 用于记录经过站点的顺序 -int t[N]; // 用于记录站点之间的路径经过的次数 -int a[N], b[N], c[N], x, y; // 用于记录每段路径所花的费用 +int n, m; // 铁路途经n个城市,要去m个城市 +int p[N]; // 记录经过站点的顺序 +int a[N], b[N], c[N]; // 记录每段路径所花的费用 +int t[N]; // 记录站点之间的路径经过的次数 +int ans; // 答案 signed main() { cin >> n >> m; for (int i = 1; i <= m; i++) cin >> p[i]; for (int i = 1; i < n; i++) cin >> a[i] >> b[i] >> c[i]; - for (int i = 1; i < m; i++) { // 所有的区间都以较小的点排在前面,例如:2-1,5-3都用1-2,3-5表示,且每一段都用前面较小的点作为标记!!!! + // 所有的区间都以较小的点排在前面,例如:2-1,5-3都用1-2,3-5表示, + // 且每一段都用前面较小的点作为标记!!!! + for (int i = 1; i < m; i++) { + int x, y; if (p[i] > p[i + 1]) { x = p[i + 1]; y = p[i]; @@ -24,9 +28,7 @@ signed main() { t[x]++; t[y]--; } - for (int i = 1; i <= n; i++) { // 求前缀和 - t[i] += t[i - 1]; - } + for (int i = 1; i <= n; i++) t[i] += t[i - 1]; // 求前缀和 for (int i = 1; i <= n - 1; i++) ans += min(a[i] * t[i], (b[i] * t[i] + c[i])); // 求总的最小就是把每一段的最小相加 cout << ans << endl; From 61863d09a4a118c8768bc630511f8528e28b5ccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sun, 17 Dec 2023 09:53:33 +0800 Subject: [PATCH 20/25] 'commit' --- TangDou/Topic/PrefixAndSuffix/P1083.cpp | 43 ++++++++++++++++ TangDou/Topic/PrefixAndSuffix/P1083_0.cpp | 28 +++++++++++ .../前缀和和差分洛谷题单总结.md | 49 +++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 TangDou/Topic/PrefixAndSuffix/P1083.cpp create mode 100644 TangDou/Topic/PrefixAndSuffix/P1083_0.cpp diff --git a/TangDou/Topic/PrefixAndSuffix/P1083.cpp b/TangDou/Topic/PrefixAndSuffix/P1083.cpp new file mode 100644 index 0000000..e260395 --- /dev/null +++ b/TangDou/Topic/PrefixAndSuffix/P1083.cpp @@ -0,0 +1,43 @@ +#include +using namespace std; +const int N = 1000010; +#define int long long +#define endl "\n" +int n, m; // 天数和订单的数量 +int r[N]; // 第i天学校有r[i]个教室可借用 +int d[N], s[N], t[N]; // 借的教室数目、从第s天借到t天 +int cf[N]; // 差分数组 +bool judge(int x) { // 判断能不能通过x个人 + memset(cf, 0, sizeof(cf)); // 每次判断都要先初始化差分数组 + int sum = 0; // 记录需要借的教室数 + for (int i = 1; i <= x; i++) { + cf[s[i]] += d[i]; // 因为只会对在s~l之间要借用教室的人产生影响,所以可以差分 + cf[t[i] + 1] -= d[i]; // 差分//注意是t[i]+1,因为要包含t[i]这个点 + } + for (int i = 1; i <= n; i++) { + sum += cf[i]; // 因为cf是差分数组,所以sum就是在第i天的借教室的总数 + if (sum > r[i]) // 如果要借的教室多于空的教室 + return false; // 不可行 + } + return true; // 可行 +} +signed main() { + cin >> n >> m; + for (int i = 1; i <= n; i++) cin >> r[i]; + for (int i = 1; i <= m; i++) cin >> d[i] >> s[i] >> t[i]; + if (judge(m)) { // 如果全部满足 + cout << '0'; // 输出0 + return 0; // 直接结束程序 + } + int l = 1; + int r = m; // 二分左右区间 + while (l < r) { + int mid = (l + r) / 2; + if (judge(mid) == true) // 如果可行 + l = mid + 1; // 增多满足人数 + else // 否则 + r = mid; // 减少满足人数 + } + cout << "-1" << endl + << l; // 输出-1和需要修改的人 +} \ No newline at end of file diff --git a/TangDou/Topic/PrefixAndSuffix/P1083_0.cpp b/TangDou/Topic/PrefixAndSuffix/P1083_0.cpp new file mode 100644 index 0000000..f65eebf --- /dev/null +++ b/TangDou/Topic/PrefixAndSuffix/P1083_0.cpp @@ -0,0 +1,28 @@ +#include +using namespace std; +int n, m; +const int N = 1000010; +int r[N]; +int main() { + cin >> n >> m; + // 每一天可租借教室数 + for (int i = 1; i <= n; i++) cin >> r[i]; + + // 从哪天到哪天,借多少个 + for (int i = 1; i <= m; i++) { + int d, s, t; + cin >> d >> s >> t; + // 从开始天到结束天 + for (int j = s; j <= t; j++) { + r[j] -= d; // 减去借走的教室数 + if (r[j] < 0) { // 小于0了! + cout << -1 << endl + << i << endl; + return 0; + } + } + } + + cout << 0 << endl; + return 0; +} \ No newline at end of file diff --git a/TangDou/Topic/前缀和和差分洛谷题单总结.md b/TangDou/Topic/前缀和和差分洛谷题单总结.md index 85da93f..b5165bd 100644 --- a/TangDou/Topic/前缀和和差分洛谷题单总结.md +++ b/TangDou/Topic/前缀和和差分洛谷题单总结.md @@ -85,6 +85,55 @@ int main() { return 0; } ``` +### [$P1083$ [$NOIP2012$ 提高组] 借教室](https://www.luogu.com.cn/problem/P1083) +![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202312170927931.png) + +#### 暴力 +还是先看暴力怎么做吧,对于$m$次借教室,我们可以每次把区间$s\sim t$的空教室数$r-=d$,当有一次$r<0$时,则当前这个人无法被满足,直接输出$-1$和当前这个人的号数,然后直接结束程序。如果$m$次借教室都操作完成后依然没有房间数$r<0$,则说明所有人都可以被满足,则输出$0$。 + +综合上述做法,得分$60$。 + +```cpp {.line-numbers} +#include +using namespace std; +int n, m; +const int N = 1000010; +int r[N]; +int main() { + cin >> n >> m; + // 每一天可租借教室数 + for (int i = 1; i <= n; i++) cin >> r[i]; + + // 从哪天到哪天,借多少个 + for (int i = 1; i <= m; i++) { + int d, s, t; + cin >> d >> s >> t; + // 从开始天到结束天 + for (int j = s; j <= t; j++) { + r[j] -= d; // 减去借走的教室数 + if (r[j] < 0) { // 小于0了! + cout << -1 << endl + << i << endl; + return 0; + } + } + } + + cout << 0 << endl; + return 0; +} +``` +显然,这样做法的时间复杂度时$O(N*M)$的,无法通过此题,从而我们可以推知该题正确的时间复杂度应该是$log$级的。 + +#### 正解 +既然时间复杂度时$log$级的,于是想到了二分。 + +再看到每个人借教室的时间可以看成一个区间,且该区间只会对其他在该区间要借教室的人产生影响,对于区间之外的借教室的人是不会产生影响的,于是又想到了差分。 + +差分序列:(可用于区间增减)记录相邻两个量的变化量,所以当在一段区间$[l,r]$上增加$a$时,只需要在$l$处加$a$,在$r+1$处$-a$即可。 + +对于为什么可以二分:如果一个人无法被满足,则他后面的人全都不能被满足;如果一个人可以被满足,则他前面的人都可以被满足,这恰恰吻合了我们二分的性质。 + ### [$P3406$ 海底高铁](https://www.luogu.com.cn/problem/P3406) ![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202312170857701.png) From 2efd9fa86150ad0e39c51c321e28fbe2c06ed965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sun, 17 Dec 2023 09:58:16 +0800 Subject: [PATCH 21/25] 'commit' --- TangDou/Topic/PrefixAndSuffix/P1083.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/TangDou/Topic/PrefixAndSuffix/P1083.cpp b/TangDou/Topic/PrefixAndSuffix/P1083.cpp index e260395..b822180 100644 --- a/TangDou/Topic/PrefixAndSuffix/P1083.cpp +++ b/TangDou/Topic/PrefixAndSuffix/P1083.cpp @@ -7,7 +7,7 @@ int n, m; // 天数和订单的数量 int r[N]; // 第i天学校有r[i]个教室可借用 int d[N], s[N], t[N]; // 借的教室数目、从第s天借到t天 int cf[N]; // 差分数组 -bool judge(int x) { // 判断能不能通过x个人 +bool check(int x) { // 判断能不能通过x个人 memset(cf, 0, sizeof(cf)); // 每次判断都要先初始化差分数组 int sum = 0; // 记录需要借的教室数 for (int i = 1; i <= x; i++) { @@ -25,18 +25,17 @@ signed main() { cin >> n >> m; for (int i = 1; i <= n; i++) cin >> r[i]; for (int i = 1; i <= m; i++) cin >> d[i] >> s[i] >> t[i]; - if (judge(m)) { // 如果全部满足 - cout << '0'; // 输出0 - return 0; // 直接结束程序 + if (check(m)) { // 如果全部满足 + cout << 0 << endl; // 输出0 + exit(0); // 直接结束程序 } - int l = 1; - int r = m; // 二分左右区间 + int l = 1, r = m; // 二分左右区间 while (l < r) { - int mid = (l + r) / 2; - if (judge(mid) == true) // 如果可行 - l = mid + 1; // 增多满足人数 - else // 否则 - r = mid; // 减少满足人数 + int mid = l + r >> 1; + if (check(mid)) // 如果可行 + l = mid + 1; // 增多满足人数 + else // 否则 + r = mid; // 减少满足人数 } cout << "-1" << endl << l; // 输出-1和需要修改的人 From d0ebfd4b92d407d08f114d62c2215475a6161370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sun, 17 Dec 2023 09:58:47 +0800 Subject: [PATCH 22/25] 'commit' --- TangDou/Topic/PrefixAndSuffix/P1083.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/TangDou/Topic/PrefixAndSuffix/P1083.cpp b/TangDou/Topic/PrefixAndSuffix/P1083.cpp index b822180..4dc4516 100644 --- a/TangDou/Topic/PrefixAndSuffix/P1083.cpp +++ b/TangDou/Topic/PrefixAndSuffix/P1083.cpp @@ -3,19 +3,19 @@ using namespace std; const int N = 1000010; #define int long long #define endl "\n" -int n, m; // 天数和订单的数量 -int r[N]; // 第i天学校有r[i]个教室可借用 -int d[N], s[N], t[N]; // 借的教室数目、从第s天借到t天 -int cf[N]; // 差分数组 -bool check(int x) { // 判断能不能通过x个人 - memset(cf, 0, sizeof(cf)); // 每次判断都要先初始化差分数组 - int sum = 0; // 记录需要借的教室数 +int n, m; // 天数和订单的数量 +int r[N]; // 第i天学校有r[i]个教室可借用 +int d[N], s[N], t[N]; // 借的教室数目、从第s天借到t天 +int b[N]; // 差分数组 +bool check(int x) { // 判断能不能通过x个人 + memset(b, 0, sizeof(b)); // 每次判断都要先初始化差分数组 + int sum = 0; // 记录需要借的教室数 for (int i = 1; i <= x; i++) { - cf[s[i]] += d[i]; // 因为只会对在s~l之间要借用教室的人产生影响,所以可以差分 - cf[t[i] + 1] -= d[i]; // 差分//注意是t[i]+1,因为要包含t[i]这个点 + b[s[i]] += d[i]; // 因为只会对在s~l之间要借用教室的人产生影响,所以可以差分 + b[t[i] + 1] -= d[i]; // 差分//注意是t[i]+1,因为要包含t[i]这个点 } for (int i = 1; i <= n; i++) { - sum += cf[i]; // 因为cf是差分数组,所以sum就是在第i天的借教室的总数 + sum += b[i]; // 因为cf是差分数组,所以sum就是在第i天的借教室的总数 if (sum > r[i]) // 如果要借的教室多于空的教室 return false; // 不可行 } From 02d810996d0737386cbf5ac49994e9f4ad0ec2d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sun, 17 Dec 2023 12:29:19 +0800 Subject: [PATCH 23/25] 'commit' --- TangDou/Topic/PrefixAndSuffix/P1083.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/TangDou/Topic/PrefixAndSuffix/P1083.cpp b/TangDou/Topic/PrefixAndSuffix/P1083.cpp index 4dc4516..683878c 100644 --- a/TangDou/Topic/PrefixAndSuffix/P1083.cpp +++ b/TangDou/Topic/PrefixAndSuffix/P1083.cpp @@ -15,9 +15,8 @@ bool check(int x) { // 判断能不能通过x个人 b[t[i] + 1] -= d[i]; // 差分//注意是t[i]+1,因为要包含t[i]这个点 } for (int i = 1; i <= n; i++) { - sum += b[i]; // 因为cf是差分数组,所以sum就是在第i天的借教室的总数 - if (sum > r[i]) // 如果要借的教室多于空的教室 - return false; // 不可行 + sum += b[i]; // 因为cf是差分数组,所以sum就是在第i天的借教室的总数 + if (sum > r[i]) return false; // 不可行,如果要借的教室多于空的教室 } return true; // 可行 } From fec930aaf016128176f1272c79a232a78b9a748f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sun, 17 Dec 2023 12:30:53 +0800 Subject: [PATCH 24/25] 'commit' --- TangDou/Topic/PrefixAndSuffix/P1083.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/TangDou/Topic/PrefixAndSuffix/P1083.cpp b/TangDou/Topic/PrefixAndSuffix/P1083.cpp index 683878c..1c2bdc3 100644 --- a/TangDou/Topic/PrefixAndSuffix/P1083.cpp +++ b/TangDou/Topic/PrefixAndSuffix/P1083.cpp @@ -3,16 +3,16 @@ using namespace std; const int N = 1000010; #define int long long #define endl "\n" -int n, m; // 天数和订单的数量 -int r[N]; // 第i天学校有r[i]个教室可借用 -int d[N], s[N], t[N]; // 借的教室数目、从第s天借到t天 -int b[N]; // 差分数组 -bool check(int x) { // 判断能不能通过x个人 - memset(b, 0, sizeof(b)); // 每次判断都要先初始化差分数组 - int sum = 0; // 记录需要借的教室数 +int n, m; // 天数和订单的数量 +int r[N]; // 第i天学校有r[i]个教室可借用 +int d[N], s[N], t[N]; // 借的教室数目、从第s天借到t天 +int b[N]; // 差分数组 +bool check(int x) { // 判断能不能通过x个人 + memset(b, 0, sizeof b); // 每次判断都要先初始化差分数组 + int sum = 0; // 记录需要借的教室数 for (int i = 1; i <= x; i++) { b[s[i]] += d[i]; // 因为只会对在s~l之间要借用教室的人产生影响,所以可以差分 - b[t[i] + 1] -= d[i]; // 差分//注意是t[i]+1,因为要包含t[i]这个点 + b[t[i] + 1] -= d[i]; // 差分,注意:是t[i]+1,因为要包含t[i]这个点 } for (int i = 1; i <= n; i++) { sum += b[i]; // 因为cf是差分数组,所以sum就是在第i天的借教室的总数 From 80ffcc3bbfe747bf050660e49ead0ec2d56c4d7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Sun, 17 Dec 2023 12:31:49 +0800 Subject: [PATCH 25/25] 'commit' --- .../前缀和和差分洛谷题单总结.md | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/TangDou/Topic/前缀和和差分洛谷题单总结.md b/TangDou/Topic/前缀和和差分洛谷题单总结.md index b5165bd..5d42dbe 100644 --- a/TangDou/Topic/前缀和和差分洛谷题单总结.md +++ b/TangDou/Topic/前缀和和差分洛谷题单总结.md @@ -134,6 +134,49 @@ int main() { 对于为什么可以二分:如果一个人无法被满足,则他后面的人全都不能被满足;如果一个人可以被满足,则他前面的人都可以被满足,这恰恰吻合了我们二分的性质。 +```cpp {.line-numbers} +#include +using namespace std; +const int N = 1000010; +#define int long long +#define endl "\n" +int n, m; // 天数和订单的数量 +int r[N]; // 第i天学校有r[i]个教室可借用 +int d[N], s[N], t[N]; // 借的教室数目、从第s天借到t天 +int b[N]; // 差分数组 +bool check(int x) { // 判断能不能通过x个人 + memset(b, 0, sizeof b); // 每次判断都要先初始化差分数组 + int sum = 0; // 记录需要借的教室数 + for (int i = 1; i <= x; i++) { + b[s[i]] += d[i]; // 因为只会对在s~l之间要借用教室的人产生影响,所以可以差分 + b[t[i] + 1] -= d[i]; // 差分,注意:是t[i]+1,因为要包含t[i]这个点 + } + for (int i = 1; i <= n; i++) { + sum += b[i]; // 因为cf是差分数组,所以sum就是在第i天的借教室的总数 + if (sum > r[i]) return false; // 不可行,如果要借的教室多于空的教室 + } + return true; // 可行 +} +signed main() { + cin >> n >> m; + for (int i = 1; i <= n; i++) cin >> r[i]; + for (int i = 1; i <= m; i++) cin >> d[i] >> s[i] >> t[i]; + if (check(m)) { // 如果全部满足 + cout << 0 << endl; // 输出0 + exit(0); // 直接结束程序 + } + int l = 1, r = m; // 二分左右区间 + while (l < r) { + int mid = l + r >> 1; + if (check(mid)) // 如果可行 + l = mid + 1; // 增多满足人数 + else // 否则 + r = mid; // 减少满足人数 + } + cout << "-1" << endl + << l; // 输出-1和需要修改的人 +} +``` ### [$P3406$ 海底高铁](https://www.luogu.com.cn/problem/P3406) ![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202312170857701.png)