整除分块
给出一道例题,已知n
, 求
∑
i
=
1
n
⌊
n
i
⌋
\sum_{i=1}^{n} \lfloor\frac{n}{i} \rfloor
∑i=1n⌊in⌋
这就是整除分块的基本例题
画一个表格找一下怎么计算,以n = 15
为例
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
⌊ 15 i ⌋ \lfloor \frac{15}{i}\rfloor ⌊i15⌋ | 15 | 7 | 5 | 3 | 3 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
证明:
假设分块的左端点为l
,要求分块的右端点r
.
设分块的值为k
,对于区间
[
l
,
r
]
[l,r]
[l,r]的每个数满足
k
=
⌊
n
i
⌋
=
⌊
n
l
⌋
k=\lfloor \frac{n}{i}\rfloor=\lfloor \frac{n}{l}\rfloor
k=⌊in⌋=⌊ln⌋, 即
k
i
≤
n
ki \leq n
ki≤n,需要找到最大的i
使其成立
可得 r = ⌊ n k ⌋ = ⌊ n n l ⌋ r = \lfloor \frac{n}{k}\rfloor=\lfloor \frac{n}{ \frac{n}{l}}\rfloor r=⌊kn⌋=⌊lnn⌋
计算的相关代码如下:
每次计算出相同值的左右端点 [ l , r ] [l, r] [l,r] ,那么相同值的个数就为 r − l + 1 r-l+1 r−l+1
int res = 0;
for(int l = 1, r; l <= n; l = r + 1)
{
r = n / (n / l);
res += n / l * (r - l + 1);
}
题目
简单版
链接:
https://codeforces.com/problemset/problem/1561/D1
状态表示:
f
[
i
]
f[i]
f[i]: i
变为1的种类数
状态转移:
f [ i ] = ∑ j = 1 i − 1 f [ j ] + ∑ j = 2 i f [ ⌊ i j ⌋ ] f[i] = \sum_{j=1}^{i-1}f[j] + \sum_{j=2}^{i}f[ \lfloor \frac{i}{j}\rfloor ] f[i]=∑j=1i−1f[j]+∑j=2if[⌊ji⌋]
前一部分是考虑减法的方程,后一部分是考虑除法的方程
- 减法:可以使用前缀和进行优化
- 除法: ⌊ i j ⌋ \lfloor \frac{i}{j}\rfloor ⌊ji⌋ 考虑使用整除分块
复杂度为 O ( n n ) O(n \sqrt n) O(nn)
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
void solve()
{
int n, m;
cin >> n >> m;
vector<ll> f(n + 1, 0), s(n + 1, 0);
f[1] = 1;
s[1] = 1;
for(int i = 2; i <= n; i++)
{
f[i] = (f[i] + s[i - 1]) % m;
for(int l = 2, r; l <= i; l = r + 1)
{
r = i / (i / l);
int cnt = r - l + 1;
ll x = f[i / l] * cnt % m;
f[i] = (f[i] + x) % m;
}
s[i] = s[i - 1] + f[i];
s[i] %= m;
}
cout << f[n] % m << "\n";
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
// cin >> t;
t = 1;
while(t--)
solve();
return 0;
}
正常版
https://codeforces.com/contest/1561/problem/D2
这道题n
的限制变大,整除分块无法过去
反过来考虑,从小到大进行考虑
状态表示:
f
[
i
]
f[i]
f[i] i
变到n
的方案数
状态转移:
f [ i ] = ∑ j = i + 1 n f [ j ] + ∑ j = 2 j ∗ i ≤ n ∑ k = i ∗ j m i n ( i ∗ j + j − 1 , n ) f [ k ] f[i] = \sum_{j=i+1}^{n}f[j] + \sum_{j=2}^{j*i\leq n} \sum_{k=i*j}^{min(i*j+j-1,n)}f[k] f[i]=∑j=i+1nf[j]+∑j=2j∗i≤n∑k=i∗jmin(i∗j+j−1,n)f[k]
状态转移公式可能看着很难懂,下面进行解释:
- 前一部分:通过加法变到
j
的方案和,i
可以变到 [ i + 1 , n ] [i+1,n] [i+1,n]的任意一个- 计算方法:可以通过后缀和进行计算
- 后一部分:通过乘法变到
k
的方案数,因为题目是除法,我们反过来就变成了乘法,除法进行考虑,我们找一下原始的一个区间,区间中的每一个值可以通过除法变到同一个值- 若除
2
,区间 [ 2 ∗ i , 2 ∗ i + 1 ] [2*i, 2*i+1] [2∗i,2∗i+1]中的数可以变到i
, 计算次数为 n / 2 n/2 n/2 - 若除
3
,区间 [ 3 ∗ i , 3 ∗ i + 2 ] [3*i, 3*i+2] [3∗i,3∗i+2]中的数可以变到i
,计算次数为 n / 3 n/3 n/3 - 若除
4
,区间 [ 4 ∗ i , 4 ∗ i + 3 ] [4*i, 4*i+3] [4∗i,4∗i+3]中的数可以变到i
,计算次数为 n / 4 n/4 n/4 - …
- 若除
j
,区间 [ j ∗ i , j ∗ i + j − 1 ] [j*i, j*i+j-1] [j∗i,j∗i+j−1]中的数可以变到i
,计算次数为 n / j n/j n/j - 统计方法:后缀和: s [ i ∗ j ] − s [ m i n ( i ∗ j + j , n + 1 ) ] s[i * j] - s[min(i * j + j, n + 1)] s[i∗j]−s[min(i∗j+j,n+1)]
- 若除
总的计算次数就是 n / 2 + n / 3 + n / 4 + . . . + n / n = n ( 1 / 2 + 1 / 3 + . . . + 1 / n ) n/2+n/3+n/4+...+n/n=n(1/2+1/3+...+1/n) n/2+n/3+n/4+...+n/n=n(1/2+1/3+...+1/n) 后面的是调和级数,复杂度为 l o g ( n ) log(n) log(n),故总的复杂度为 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
void solve()
{
int n, m;
cin >> n >> m;
vector<ll> f(n + 2, 0), s(n + 2, 0);
f[n] = 1;
s[n] = 1;
for(int i = n - 1; i >= 1; i--)
{
f[i] = (f[i] + s[i + 1]) % m;
for(int j = 2; j * i <= n; j++)
{
// [i * j, i * j + j - 1]
f[i] = (f[i] + s[i * j] - s[min(i * j + j, n + 1)]) % m;
f[i] = (f[i] + m) % m;
}
s[i] = (s[i + 1] + f[i]) % m;
}
cout << f[1] << "\n";
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
// cin >> t;
t = 1;
while(t--)
solve();
return 0;
}