Loading

牛客 | 小G的约数引起的对于 整数分块 学习

整除分块是个啥:要求\(∑_{i = 1}^n{n/i}\) 的值,这时候暴力需要O(n)的时间。由于这个区间是连续的,且’/'是向下取整,当i不能整除k时,n/i会等于最小的i(也就是区间最左边的值 L)除n的商。此时如果可以很快的找到这一个区间,那么就可以将时间复杂度降到\(O(\sqrt{n})\)。 接下来讲一下怎么去找这个区间:
假设 n = 20,然后打表

i 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
20/i 20 10 6 5 4 3 2 2 2 2 1 1 1 1 1 1 1 1 1 1

看到这个表不难发现规律,用20再去除以 (20/i) 就等于最后一个等于这个值的数,比如说 当i=7时,20/i=2,那么用20/(20/7) = 10, 这个时候10就是20/i等于2的最后一个值。可以利用这个特性,在区间最左边用O(1)的时间就可以计算出区间最右边的坐标。在这个区间内,所有的值都是相同的,所以找到这个区间后,直接用区间长度乘以单个数值就ok。

// Core Code
ll G(ll n) {
    ll cnt = 0;
    for (int l = 1, r; l <= n; l = r + 1) {
        r = n / (n / l); // 区间最右边
        cnt += (n / l) * (r - l + 1);
    }
    return cnt;
}

Next:余数求和 要求\(∑_{i = 1}^n{k\ \%\ i}\)

\[∑_{i = 1}^n{k\ \%\ i} = ∑_{i = 1}^n{k - i * (k / i)} = n * k - ∑_{i = 1}^n{i * (k/i)} \]

在每一段(L,R)中 k/i = k/L ,所以在相加的时候可以当作公因式提出来。\(∑_{i = 1}^ni\)相当于一个等差数列。由等差数列求和公式可得: (R-L+1) * (L+R) / 2。

所以每一段(L,R)的和可以表示为 k/L * (R-L+1) * (L+R) / 2。

// RioTian 21/03/03
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll n, k, cnt;
int main() {
    cin >> n >> k;
    cnt = n * k;
    for (ll l = 1, r; l <= n; l = r + 1) {
        if (k / l != 0)
            r = min(n, k / (k / l));
        else
            r = n;
        cnt -= (k / l) * (r - l + 1) * (l + r) / 2;
    }
    cout << cnt << "\n";
    return 0;
}
posted @ 2021-03-03 20:27  RioTian  阅读(70)  评论(1)    收藏  举报