整除分块(数论分块)

文章基于b站大佬的视频——整除分块 by泥土笨笨

引例

∑ i = 1 n ⌊ n i ⌋ \sum_{i=1}^{n}{\left \lfloor \frac{n}{i} \right \rfloor} i=1nin

其中 n ≤ 1 0 12 n\le10^{12} n1012,我们打表观察下规律

i12345678910
⌊ n i ⌋ \left \lfloor \frac{n}{i} \right \rfloor in10532211111

n = 10 n=10 n=10为例,表中同样的值会连续出现,而相同的值所划分的区间积是整除分块。整除的性质使得从1到n的 ⌊ n i ⌋ \left \lfloor \frac{n}{i} \right \rfloor in可根据数值划分为不同的分块,且分块数远远小于n。利用这种性质,我们如果能推导出每个分块具体的左右端点位置在哪,这个问题就可以快速求解出来了。

整除分块的性质

性质1 ⌊ n i ⌋ \left \lfloor \frac{n}{i} \right \rfloor in最多只有 2 n 2\sqrt{n} 2n 种取值

证明:对于 i ≤ n i\le \sqrt{n} in ,最多只有 n \sqrt{n} n 种取值,而对于 i > n i>\sqrt{n} i>n ,有 n i < n \frac{n}{i}<\sqrt{n} in<n ,也最多只有 n \sqrt{n} n 种取值,所以总的值最多不超过 2 n 2\sqrt{n} 2n

性质2: 若 ⌊ n r ⌋ \left \lfloor \frac{n}{r} \right \rfloor rn ⌊ n i ⌋ \left \lfloor \frac{n}{i} \right \rfloor in相等,那么 r r r的最大取值为
⌊ n ⌊ n i ⌋ ⌋ \left \lfloor \frac{n}{\left \lfloor \frac{n}{i} \right \rfloor} \right \rfloor inn

证明:设 ⌊ n i ⌋ = k \left \lfloor \frac{n}{i} \right \rfloor=k in=k,于是如果 r r r取到比较大的数字,使得 ⌊ n r ⌋ \left \lfloor \frac{n}{r} \right \rfloor rn ⌊ n i ⌋ \left \lfloor \frac{n}{i} \right \rfloor in不相等,那么 ⌊ n r ⌋ < k \left \lfloor \frac{n}{r} \right \rfloor < k rn<k,也即 n r < k \frac{n}{r} < k rn<k,也即 r > n k r>\frac{n}{k} r>kn,那么 r > ⌊ n k ⌋ r>\left \lfloor \frac{n}{k} \right \rfloor r>kn,即 r > ⌊ n ⌊ n i ⌋ ⌋ r>\left \lfloor \frac{n}{\left \lfloor \frac{n}{i} \right \rfloor} \right \rfloor r>inn。所以 r r r最大取到 ⌊ n ⌊ n i ⌋ ⌋ \left \lfloor \frac{n}{\left \lfloor \frac{n}{i} \right \rfloor} \right \rfloor inn可以使得 ⌊ n r ⌋ \left \lfloor \frac{n}{r} \right \rfloor rn ⌊ n i ⌋ \left \lfloor \frac{n}{i} \right \rfloor in相等

代码演示:

for(int l = 1;l <= n;l = r + 1) {
	r = n / (n / l);
}

例题

练习1:

已知正整数 n , a , b n,a,b n,a,b,求
∑ i = 1 n ⌊ n a i + b ⌋ \sum_{i=1}^{n}{\left \lfloor \frac{n}{ai+b} \right \rfloor} i=1nai+bn
证明:设 ⌊ n a i + b ⌋ = k \left \lfloor \frac{n}{ai+b} \right \rfloor=k ai+bn=k,于是如果 r r r取到比较大的数字,使得 ⌊ n a r + b ⌋ \left \lfloor \frac{n}{ar+b} \right \rfloor ar+bn ⌊ n a i + b ⌋ \left \lfloor \frac{n}{ai+b} \right \rfloor ai+bn不相等,那么 ⌊ n a r + b ⌋ < k \left \lfloor \frac{n}{ar+b} \right \rfloor < k ar+bn<k,也即 n a r + b < k \frac{n}{ar+b} < k ar+bn<k,也即 r > n − k b a k r>\frac{n-kb}{ak} r>aknkb,那么 r > ⌊ n − k b a k ⌋ r>\left \lfloor \frac{n-kb}{ak} \right \rfloor r>aknkb。所以 r r r最大取到 ⌊ n − k b a k ⌋ \left \lfloor \frac{n-kb}{ak} \right \rfloor aknkb ⌊ n a r + b ⌋ = ⌊ n a i + b ⌋ \left \lfloor \frac{n}{ar+b} \right \rfloor=\left \lfloor \frac{n}{ai+b} \right \rfloor ar+bn=ai+bn.

练习2:

P2261 [CQOI2007]余数求和

题目描述

给出正整数 n n n k k k,请计算

G ( n , k ) = ∑ i = 1 n k   m o d   i G(n, k) = \sum_{i = 1}^n k \bmod i G(n,k)=i=1nkmodi

其中 k   m o d   i k\bmod i kmodi 表示 k k k 除以 i i i 的余数。

输入格式

输入只有一行两个整数,分别表示 n n n k k k

输出格式

输出一行一个整数表示答案。

样例 #1
样例输入 #1
10 5
样例输出 #1
29
提示
样例 1 解释

G ( 10 , 5 ) = 0 + 1 + 2 + 1 + 0 + 5 + 5 + 5 + 5 + 5 = 29 G(10, 5)=0+1+2+1+0+5+5+5+5+5=29 G(10,5)=0+1+2+1+0+5+5+5+5+5=29

数据规模与约定
  • 对于 30 % 30\% 30% 的数据,保证 n , k ≤ 1 0 3 n , k \leq 10^3 n,k103
  • 对于 60 % 60\% 60% 的数据,保证 n , k ≤ 1 0 6 n, k \leq 10^6 n,k106
  • 对于 100 % 100\% 100% 的数据,保证 1 ≤ n , k ≤ 1 0 9 1 \leq n, k \leq 10^9 1n,k109
解题思路

首先,我们知道
k % i = k − i ⌊ k i ⌋ k\%i=k-i \left \lfloor \frac{k}{i} \right \rfloor k%i=kiik
所以
∑ i = 1 n k   m o d   i = ∑ i = 1 n ( k − i ⌊ k i ⌋ ) = ∑ i = 1 n k − ∑ i = 1 n i ⌊ k i ⌋ = n k − ∑ i = 1 n i ⌊ k i ⌋ \sum_{i=1}^{n}{k\ mod\ i}=\sum_{i=1}^{n}{(k-i\left \lfloor \frac{k}{i} \right \rfloor)}=\sum_{i=1}^{n}{k}-\sum_{i=1}^{n}{i\left \lfloor \frac{k}{i} \right \rfloor}=nk-\sum_{i=1}^{n}{i\left \lfloor \frac{k}{i} \right \rfloor} i=1nk mod i=i=1n(kiik)=i=1nki=1niik=nki=1niik

∑ i = 1 n i ⌊ k i ⌋ \sum_{i=1}^{n}{i\left \lfloor \frac{k}{i} \right \rfloor} i=1niik
可以用除法分块来求,每次求一个 ⌊ k i ⌋ \left \lfloor \frac{k}{i} \right \rfloor ik值固定的区间 [ l , r ] [l,r] [l,r],这时候把 ⌊ k i ⌋ \left \lfloor \frac{k}{i} \right \rfloor ik提出来,剩下部分是一个等差数列,直接用公式求和,再乘以 ⌊ k i ⌋ \left \lfloor \frac{k}{i} \right \rfloor ik即可

代码
#include<iostream>
using namespace std;
typedef long long ll;
int main()
{
    ll n, k, r, l;
    cin >> n >> k;
    ll ans = n * k;
    for (l = 1, r; l <= n; l = r + 1) {
        if (k / l == 0) break;
        r = min(n, k / (k / l));
        ans -= (k / l) * (l + r) * (r - l + 1) / 2;
    }
    cout << ans;
    return 0;
}

练习3:

P6003 [USACO20JAN] Loan Repayment S

题目描述

Farmer John 欠了 Bessie N N N 加仑牛奶( 1 ≤ N ≤ 1 0 12 1 \leq N \leq 10^{12} 1N1012)。他必须在 K K K 天内将牛奶给 Bessie。但是,他不想将牛奶太早拿出手。另一方面,他不得不在还债上有所进展,所以他必须每天给 Bessie 至少 M M M 加仑牛奶( 1 ≤ M ≤ 1 0 12 1 \leq M \leq 10^{12} 1M1012)。

以下是 Farmer John 决定偿还 Bessie 的方式。首先他选择一个正整数 X X X。然后他每天都重复以下过程:

  1. 假设 Farmer John 已经给了 Bessie G G G 加仑,计算 N − G X \frac{N-G}{X} XNG 向下取整。令这个数为 Y Y Y
  2. 如果 Y Y Y 小于 M M M,令 Y Y Y 等于 M M M
  3. 给 Bessie Y Y Y 加仑牛奶。

X X X 的最大值,使得 Farmer John 按照上述过程能够在 K K K 天后给 Bessie 至少 N N N 加仑牛奶 ( 1 ≤ K ≤ 1 0 12 1 \leq K \leq 10^{12} 1K1012)。

输入格式

输入仅有一行,包含三个空格分隔的正整数 N , K , M N,K,M N,K,M,满足 K × M < N K \times M<N K×M<N

输出格式

输出最大的正整数 X X X,使得按照上述过程 Farmer John 会给 Bessie 至少 N N N 加仑牛奶。

样例 #1
样例输入 #1
10 3 3
样例输出 #1
2
提示
样例解释

在这个测试用例中,当 X = 2 X=2 X=2 时 Farmer John 第一天给 Bessie 5 5 5 加仑,后两天每天给 Bessie M = 3 M=3 M=3 加仑。

子任务
  • 测试点 2 ∼ 4 2 \sim 4 24 满足 K ≤ 1 0 5 K \leq 10^5 K105
  • 测试点 5 ∼ 11 5 \sim 11 511 没有额外限制。
解题思路

题目中说求 X X X的最大值,那么直接对其二分即可, X X X越大还完牛奶的天数越长,显然满足二分单调性。

对于二分的 c h e c k check check函数,设 g g g是现在已经还的牛奶量,那么今天要还的 y = n − g x y=\frac{n-g}{x} y=xng,假设持续 d d d天,每天都还这么多,那么有
⌊ n − g − y d x ⌋ = y \left \lfloor \frac{n-g-yd}{x} \right \rfloor=y xngyd=y
去掉取整符号,也就是
n − g − y d x ≥ y \frac{n-g-yd}{x} \ge y xngydy
整理一下可以得到
d ≤ n − g − x y y d \le \frac{n-g-xy}{y} dyngxy
所以 d d d最大可以取到
⌊ n − g − x y y ⌋ \left \lfloor \frac{n-g-xy}{y} \right \rfloor yngxy
注意,当 d = 0 d=0 d=0时说明只能还今天的,第二天需要还的牛奶量就不一样了,所以实际上分块的大小为 d + 1 d+1 d+1

代码
#include<iostream>
#include<string>
#include<vector>
#include<cmath>
#include<algorithm>
#include<set>
#include<stack>
#include<map>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
ll n, k, m;
bool check(ll x) {
    ll g = 0, i = 1;
    while (i <= k) {
        ll y = (n - g) / x;
        if (y < m) break;
        ll day = (n - g - x * y) / y;
        // 保持每天还y个,最多持续day天,注意如果day=0意味着还一天
        ++day;
        if (i + day > k) day = k - i + 1;
        i += day;
        g += y * day;
        if (g >= n) return true;
    }
    return (k - i + 1) * m + g >= n;
}
int main()
{
    cin >> n >> k >> m;
    ll l = 1, r = n, ans = 0;
    while (l <= r) {
        ll mid = (r - l) / 2 + l;
        if (check(mid)) ans = mid, l = mid + 1;
        else r = mid - 1;
    }
    cout << ans;
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值