C/C++教程

AcWing 199. 余数之和

本文主要是介绍AcWing 199. 余数之和,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

题目传送门

零、参考资料

总结与思考:数论分块

【数学】数论分块(整除分块)

一、数论分块的相关概念

“数论分块”这个名词,其实比较模糊,没有一个广泛认同的严格定义。这里讲一下我个人的理解:
令\(\displaystyle f(i)=\lfloor \frac{n}{i} \rfloor\)
\(f(i)\)的值,随着\(i\)的增加而单调不增,如果我把\(f(1),f(2),\dots,f(n)\)从左到右排开,会发现其值呈现出“块状”,每一个“块”就是连续的一段,每个“块”中\(f(i)\)的值都是相同的
举个例子,\(n = 5\)
\(f(1)=5,f(2)=2,f(3)=f(4)=f(5)=1\),一字排开,得到\(5,2,1,1,1\),相同的分到一个“”中,直观一点,写成:\([5],[2],[1,1,1]\)

数论分块从直观上来讲就是这个现象

数论分块中,块的右端是个很重要的位置。比如例子\(n=5\)中,三个块右端的编号分别是\(1,2,5\)

二、关于\(\displaystyle \lfloor \frac{n}{i} \rfloor\)的值域大小:

  • 当\(1 ≤ i ≤ ⌊ \sqrt{n} ⌋\)时,\(\displaystyle \lfloor \frac{n}{i} \rfloor\)的不同数值个数显然是不超过\(\lfloor \sqrt n \rfloor\)。

  • 当\(\displaystyle \lfloor \sqrt n \rfloor < i \le n\)时,因为\(\displaystyle 1 \le \lfloor \frac{n}{i} \rfloor \le \sqrt n\),所以不同数值个数还是不超过\(\lfloor \sqrt n \rfloor\)。

综合上述两种情况,\(\displaystyle \lfloor \frac{n}{i} \rfloor\)的不同数值个数严格不大于\(2 \sqrt n\)

三、结论一:假设某个块的\(f\)值为\(t\),那么这个块右端的下标是$ \displaystyle \lfloor \frac{n}{t} \rfloor$

如果一个块的\(f\)值是\(t\),对于其中的数\(x\),应当满足\(\displaystyle \lfloor \frac{n}{x} \rfloor=t\),即\(n = t x + r ( 0 ≤ r < x )\),从这个式子中就可以看出\(x\)的取值是连续的一段整数。块的右端就是满足上式的最大\(x\),也就是说\(x\times t ≤ n\)的最大\(x\),那么显然\(\large \displaystyle x_{max}= \lfloor \frac{n}{t} \rfloor\)

四、结论2: \(f(i)\)的值域和块右端下标集合是相同的

比如上面那个例子,值域是\(\{1,2,5\}\),块右端下标集合也是\(\{1,2,5\}\)

解释

假设一个块的\(f\)值是\(t\),右端是\(x\),那么\(\displaystyle \lfloor \frac{n}{t} \rfloor=x, \lfloor \frac{n}{x} \rfloor=t\)

也就是说\(x\)和\(t\)一一对应,不同块的\(f\)值不会相同,一个\(f\)值也不会对应多个块,这就说明了块“右端“集合和\(f\)的值域值域这两个集合大小相同。

那么元素是否也是相同的呢?是的,因为假设一个元素\(p\)属于"块右端"集合,那么根据“块右端”的计算方法,得知某个肯定存在某个\(t\)使得\(\displaystyle \lfloor \frac{n}{t} \rfloor=p\),也就是\(f(t)=p\),也就是说\(p\)也属于\(f\)的值域集合。

因为两个集合大小相同,而且“块右端”集合中的每个元素也在值域集合中出现,所以这两个集合相等。

五、结论3: 当\(x \le \lfloor \sqrt n \rfloor\)时,\(f(x)\)的值严格单调增

六、本题解析

原式:$$\large \sum_{i=1}^{n} k \ % \ i$$

子项变形:

\[\large k \% i=k - \left \lfloor \frac{k}{i} \right \rfloor \times i \]

将子项变形代入原式:

\[\large \sum_{i=1}^{n} k \ \% \ i= n\times k - \sum_{i=1}^{n} \left \lfloor \frac{k}{i} \right \rfloor \times i \]

显然,\(\large \displaystyle ⌊\frac{k}{i}⌋\) 是最棘手的,如果暴力枚举\(i\),数据范围是\(10^9\),肯定会\(TLE\),要想办法优化。

根据前面我们准备好的知识储备,我们知道这个答案由连续 \(2\sqrt{k}\) 段不同的取值组成,我们枚举下届,并利用公式计算出上界:每种取值下界 \(l\) 和 上界 \(r\), 通过

\[\large \sum_{i=l}^{r} \left \lfloor \frac{k}{i} \right \rfloor * i =\sum_{i=l}^{r} \left \lfloor \frac{k}{l} \right \rfloor * i = \left \lfloor \frac{k}{l} \right \rfloor * \sum_{i=l}^{r}i \]

即可求得每一段对答案的贡献。 其中\(\displaystyle \sum_{i=l}^{r}i\)可以用等差数列求和公式计算:\(\displaystyle \sum_{i=l}^{r}i=\frac{(l+r)*(r-l+1)}{2}\)


七、算法设计

算法就很好设计了:

设 \(l=1\),算出上界 \(r\)。计算这段的贡献

使 \(l=r+1\),即跳到下一段计算贡献。

重复直到算完 \([1,n]\) 里所有段。

八、\(Tips\)

当 \(⌊k/l⌋=0\) 的时候,显然这段以及后面(有单调性)已经没有贡献了,可以 break
注意右端点和 \(n\) 取个 \(min\),因为 \(>n\) 没有贡献了。

九、实现代码

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
//数论分块,余数之和,本题是很多其它题的基础,需要背诵
// j(n,k)=k%1+k%2+k%3+…+k%n
LL n, k, l, r;
LL ans;
int main() {
    // 1、当k/l=0的时候,显然这段以及后面(有单调性)已经没有贡献了,可以 break。
    // 2、注意右端点和n取个min,因为>n没有贡献了。
    cin >> n >> k;
    ans = n * k;
    for (l = 1; l <= n; l = r + 1) { //枚举左端点,每次跳着走,下次的位置就是本次r的位置+1
        if (k / l == 0) break;
        r = min(k / (k / l), n); //右边界
        //等差数列求和:左到右边界内,是公差为1的等差数列,
        ans -= (k / l) * (l + r) * (r - l + 1) / 2; //首项+末项 乘以 项数 除以2
    }
    printf("%lld\n", ans);
    return 0;
}
这篇关于AcWing 199. 余数之和的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!