CQOI 2007 余数之和
题意
求 ∑i=1nkmodi,其中 1≤n,k≤109。
思路
因为 kmodi=k−⌊ik⌋⋅i,所以 ∑i=1nkmodi=∑i−1nk−⌊ik⌋⋅i=nk−∑i−1n⌊ik⌋⋅i。nk 易求,专门考虑该式后半部分 ∑i−1n⌊ik⌋⋅i。
以下过程与《算法竞赛进阶指南》有差别。
不难发现 ⌊ik⌋ 单调不上升,也就是说,⌊ik⌋ 的同一个值一定会连续出现。
不妨设 ⌊ik⌋=x 时 i 的取值范围为 lx≤i≤rx,那么应用等差数列求和公式可得 ∑i=1n⌊ik⌋⋅i=∑(x∑i=lxrxi)=∑21x(lx+rx)(rx−lx+1)。
为了计算的方便,枚举 l 而不是 x。枚举从 1 开始,每算完一段就令 l=r+1。显然 x=⌊lk⌋ ,难点在于求出 r。
关于 r 的取值,下面给出较清晰的推导,方便理解。
- 根据我们对右端点 r 的定义以及数列的单调不升性,⌊rk⌋=x 且 ⌊r+1k⌋<x。根据向下取整的定义,⌊a⌋ 为整数且 ⌊a⌋≤a<⌊a⌋+1。
- 对于 ⌊rk⌋=x 这部分,有 rk≥⌊rk⌋=x,而 r,x 都是正数,则 xk≥r。
- 对于 ⌊r+1k⌋<x 这部分,因为 ⌊r+1k⌋ 与 x 都是整数,所以 ⌊r+1k⌋+1≤x。又因为 r+1k<⌊r+1k⌋+1,所以 r+1k<x。进一步得到 xk<r+1。
- 上述两个结论连起来,得到 r≤xk<r+1。又因为 r 为整数,所以 r=⌊xk⌋。
最后发现,此处得到的结论与书中结论相同。
实现
NowCoder 4ms。Luogu 40ms,单点3~5ms。(似乎两个网站用时计算方法不一样。)
注意边界。因为 ⌊xk⌋>n 的情况可能存在,需要 r = min(k / x, n);
一下。
代码中另外有一点解释一下。我在 for
循环的判断条件中,加入书中没有的 l <= k
。这是因为当 l>k 时,显然 x 等于 0,没有必要再算了。
必须注意 long long
的问题,l, r, x
不开 long long
会WA。
#include <cstdio>
#include <algorithm>
using namespace std;
long long n, k, ans;
int main() {
scanf("%lld%lld", &n, &k);
ans = n * k;
long long l, r, x;
for (l = 1; l <= n && l <= k; l = r + 1) {
x = k / l;
r = min(k / x, n);
ans -= x * (l + r) * (r - l + 1) / 2;
}
printf("%lld\n", ans);
return 0;
}
写在最后
虽然基于教材做例题,但是推导还是必须自己推导的。
似乎自己这样的推导在思路上比较容易想到。当然在做题过程中还是要用 n=k=20 之类的小数据试一下的。
写题解时书不在身边,印象中书中只说明了从 l 到 r 除法取整的结果相同,但是没有说明 r 和 r+1 结果不同。
据说这题的方法有个专门的名字“分块除法”。该算法实际上就是根据整除的结果分成多个部分,每个部分分别计算。最典型的分块除法问题就是本题中 ∑i−1n⌊ik⌋⋅i。