洛谷 P2261 [CQOI2007]余数求和【除法分块】

文章目录

    • 思路1 暴力枚举
    • 思路2 除法分块

题意:求出 ∑ i = 1 n k   m o d   i \sum_{i=1}^n {k\ mod\ i} i=1nk mod i 的结果。

思路1 暴力枚举

一开始直接暴力枚举, O(n) \text{O(n)} O(n) 的复杂度:

#include 
using namespace std;

int main() {
     
	int n, k;
	scanf("%d%d", &n, &k);
	long long ans = 0;
	for (int i = 1; i <= n; ++i) {
     
		ans += (k % i);
	}
	printf("%lld", ans);
	return 0;
}

结果是4个TLE:
洛谷 P2261 [CQOI2007]余数求和【除法分块】_第1张图片

最大 1 0 9 10^9 109 的数据注定会卡掉暴力,因此,需要优化:对于 n > k 的部分,直接先用求和公式加上 (n - k) * k ,然后 n = k 。不过这样也还是会TLE。需要更快的方法。


思路2 除法分块

X = ∑ i = 1 n k   %   i X = \sum_{i=1}^n {k\ \%\ i} X=i=1nk % i 。且有 a   %   b = a − b ∗ ⌊ a b ⌋ a\ \%\ b = a - b * \lfloor \frac{a}{b} \rfloor a % b=abba ,则:
X = ∑ i = 1 n k − i ∗ ⌊ k i ⌋ = n ∗ k − ∑ i = 1 n i ∗ ⌊ k i ⌋ X = \sum_{i = 1}^n {k - i * \lfloor \frac{k}{i} \rfloor} = n * k - \sum_{i = 1}^n i * \lfloor \frac{k}{i} \rfloor X=i=1nkiik=nki=1niik

然后,用样例中的 n = 10, k = 5 来打表:

i 1 2 3 4 5 6 7 8 9 10
⌊ k i ⌋ \lfloor \frac{k}{i} \rfloor ik 5 2 1 1 1 0 0 0 0 0

不难发现, ⌊ k i ⌋ \lfloor \frac{k}{i} \rfloor ik 的取值在一定的区域内相等。扩大数据范围到 i = 100, k = 50 再打一次表:

#include 
using namespace std;

int main() {
      
	set<int> st;
	for (int i = 1; i <= 100; ++i) {
     
		cout << (50 / i) << "  ";
		if (i % 10 == 0) cout << endl;
		st.insert(50 / i);
	}
	cout << endl << st.size() << endl;
	return 0;
}

结果如下,只有 14 种取值:

50  25  16  12  10  8  7  6  5  5
4  4  3  3  3  3  2  2  2  2
2  2  2  2  2  1  1  1  1  1
1  1  1  1  1  1  1  1  1  1
1  1  1  1  1  1  1  1  1  1
0  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0

14

因此,对于类似 ∑ i = 1 n f ( ⌊ k i ⌋ ) \sum_{i = 1}^n f(\lfloor \frac{k}{i} \rfloor) i=1nf(ik) 的式子,如果采用第一种思路,无论如何复杂度都是 O(n) \text{O(n)} O(n)

但是, ⌊ k i ⌋ \lfloor \frac{k}{i} \rfloor ik 的取值并没有 n n n 个,而是呈现阶梯状分布,某一段区间内连续的 ⌊ k i ⌋ \lfloor \frac{k}{i} \rfloor ik 是相同的。总得来说,大概有 k \sqrt {k} k 种不同的取值。由此,可以将复杂度降低到 O ( k ) O(\sqrt{k}) O(k )

这里让 f ( ⌊ k i ⌋ ) = ⌊ k i ⌋ f(\lfloor \frac{k}{i} \rfloor) = \lfloor \frac{k}{i} \rfloor f(ik)=ik ,可以证明:

  • 如果 1 ≤ i ≤ k 1 \leq i \leq \sqrt {k} 1ik ,则最多有 k \sqrt {k} k 种结果,因为 i i i 的取值都只有 k \sqrt {k} k 种;
  • 如果 i > k i > \sqrt {k} i>k ,那么, ⌊ k i ⌋ < k \lfloor \frac{k}{i} \rfloor < \sqrt{k} ik<k ,取值也不超过 k \sqrt{k} k 种。

因此,可以使用整除分块的方法解决问题。现在,还剩下的问题是,如何确定每个块的左右端点值 i i i

  • 首先,枚举当前除法块的左边界 l l l ,一开始的 l = 1 l = 1 l=1 (题意);
  • 接着根据左边界 l l l k k k 算出右边界 r r r
  • t = ⌊ k l ⌋ t = \lfloor \frac{k}{l} \rfloor t=lk t t t 是整个除法块共同的取值,不相等的绝不是同一块。然后根据 l , t l,t l,t 来找 r r r ,分情况讨论:
    • t ≠ 0 t \neq 0 t=0 ,则 r = min ⁡ ( ⌊ k t ⌋ , n ) r = \min(\lfloor \frac{k}{t} \rfloor, n) r=min(tk,n) 。让 r = ⌊ k t ⌋ r = \lfloor \frac{k}{t} \rfloor r=tk 是为了算出有这个整除的商 t t t 的最大 i i i 是多少,加上一个 min ⁡ \min min 是为了防止越界。
    • t = 0 t = 0 t=0 ,则 r = n r = n r=n 。因此后面的一块一定都是 i > k i > k i>k 的那一段,所以此时我们直接让 r = n r = n r=n ,处理最后的这一大块。

因为要求 ∑ i = 1 n i ∗ ⌊ k i ⌋ \sum_{i = 1}^n i * \lfloor \frac{k}{i}\rfloor i=1niik ,左右边界有了,每一块的和就是 ⌊ k l ⌋ ∗ ∑ i = l r i = \lfloor \frac{k}{l}\rfloor * \sum_{i = l}^r i = lki=lri= 当前块的 t   × t\ \times t × 当前块元素个数 × \times × 当前块的平均值 = t × ( r − l + 1 ) × ( l + r ) ÷ 2 = t \times (r - l + 1) \times (l + r) \div 2 =t×(rl+1)×(l+r)÷2 。即式子前半段是一个整除分块,后半段是一个首项为 l l l 、公差为 1 1 1 的等差数列。用 n ∗ k n * k nk 减去这一块的和后,令 l = r + 1 l = r + 1 l=r+1 ,继续计算下一块,直到计算到 n n n

代码如下:

#include 
using namespace std;
typedef long long ll;
int main() {
     
	ll n, k;
	scanf("%lld%lld", &n, &k);
	ll ans = n * k;
	for (ll l = 1, r; l <= n; l = r + 1) {
     
		if (k / l != 0) r = min(k / (k / l), n);
		else r = n;
		ans -= (k / l) * (r - l + 1) * (l + r) / 2;
		//cout << "l: " << l << ", r: " << r << ", " << ans << endl;
	}
	printf("%lld", ans);
	return 0;
}

结果:
洛谷 P2261 [CQOI2007]余数求和【除法分块】_第2张图片

除法分块和积性函数结合起来,在莫比乌斯反演优化中也有很重要的作用,算是前置知识吧。

你可能感兴趣的:(洛谷,数学)