【算法讲6:数论分块(整除分块)】引入 | 思路证明 | 例题

引入

  • 给你一个正整数 n n n ,让你求
    ∑ n i = 1 ⌊ n i ⌋ \underset{i=1}{\overset{n}{\sum}}\lfloor\frac{n}{i}\rfloor i=1nin
    其中 1 ≤ n ≤ 1 0 9 1\le n\le 10^9 1n109 ⌊ x ⌋ \lfloor x\rfloor x 表示 x x x 向下取整, ∑ n i = 1 f ( i ) \underset{i=1}{\overset{n}{\sum}}f(i) i=1nf(i) 表示求 i = 1 , 2 , ⋯   , n i=1,2,\cdots,n i=1,2,,n时候, f ( i ) f(i) f(i) 的和。

思路

  • 我们首先考虑,暴力 f o r for for 循环做的话,时间复杂度为 O ( n ) O(n) O(n),会超时肯定不行。
  • 我们其次考虑打表,看看有什么规律。当 n = 30 n=30 n=30 时候:
i123456789101112131415161718192021222324252627282930
⌊ n i ⌋ \lfloor \frac{n}{i}\rfloor in301510765433322222111111111111111
  • 可以看到,对于某一段 i i i ⌊ n i ⌋ \lfloor \frac{n}{i}\rfloor in 的值都相同。
    我们能不能这样一段一段地枚举,然后分别对每一段计算贡献呢?
  • 如果我们知道了每一段的左端点 L L L 和右端点 R R R
    那么这一段的贡献容易得到: ( R − L + 1 ) × ⌊ n L ⌋ (R-L+1)\times\lfloor \frac{n}{L}\rfloor (RL+1)×Ln
    并且我们也可以轻松得到下一段的左端点为目前右端点+1
  • 那么最难问题来了:已知左端点 L L L ,怎么求这一段的右端点 R R R
    转变成数学语言,就是已知 L L L ,求 R max ⁡ R_{\max} Rmax,使得 ⌊ n L ⌋ = ⌊ n R ⌋ \lfloor \frac{n}{L}\rfloor=\lfloor \frac{n}{R}\rfloor Ln=Rn
    我们设 ⌊ n L ⌋ = k \lfloor \frac{n}{L}\rfloor=k Ln=k,即 n = L k + p n=Lk+p n=Lk+p 0 ≤ p < L 0\le p<L 0p<L
    我们设 R = L + d R=L+d R=L+d,带入得到 ⌊ n L + d ⌋ = k \lfloor \frac{n}{L+d}\rfloor=k L+dn=k,即 n = ( L + d ) k + p ′ n=(L+d)k+p^\prime n=(L+d)k+p 0 ≤ p ′ < L 0\le p^\prime<L 0p<L
    下面式子减去上面式子,得到 p ′ = p − d k p^\prime=p-dk p=pdk
    此时 d max ⁡ = ⌊ p k ⌋ d_{\max}=\lfloor\frac{p}{k}\rfloor dmax=kp,带入 R = L + d R=L+d R=L+d 得到
    R = L + ⌊ p k ⌋ = L + ⌊ n % L ⌊ n L ⌋ ⌋ = L + ⌊ n − ⌊ n L ⌋ L ⌊ n L ⌋ ⌋ = ⌊ L + n − ⌊ n L ⌋ L ⌊ n L ⌋ ⌋ = ⌊ ⌊ n L ⌋ L ⌊ n L ⌋ + n − ⌊ n L ⌋ L ⌊ n L ⌋ ⌋ = ⌊ n ⌊ n L ⌋ ⌋ \begin{aligned} R&=L+\lfloor\frac{p}{k}\rfloor\\ &=L+\lfloor\frac{n\%L}{\lfloor\frac{n}{L}\rfloor}\rfloor\\ &=L+\lfloor\frac{n-\lfloor\frac{n}{L}\rfloor L}{\lfloor\frac{n}{L}\rfloor}\rfloor\\ &=\lfloor L+\frac{n-\lfloor\frac{n}{L}\rfloor L}{\lfloor\frac{n}{L}\rfloor}\rfloor\\ &=\lfloor\frac{\lfloor\frac{n}{L}\rfloor L}{\lfloor\frac{n}{L}\rfloor}+\frac{n-\lfloor\frac{n}{L}\rfloor L}{\lfloor\frac{n}{L}\rfloor}\rfloor\\ &=\lfloor\frac{n}{\lfloor\frac{n}{L}\rfloor}\rfloor \end{aligned} R=L+kp=L+Lnn%L=L+LnnLnL=L+LnnLnL=LnLnL+LnnLnL=Lnn
    我们得到了,对于某一段的 L L L ,这时 R = ⌊ n ⌊ n L ⌋ ⌋ R=\lfloor\frac{n}{\lfloor\frac{n}{L}\rfloor}\rfloor R=Lnn
  • 最后一个问题:该算法的时间复杂度是多少?
    易得,对于一个数 n n n ,该数字的正因子的个数是 O ( 2 n ) O(2\sqrt n) O(2n ) 级别的
    数论分块的时间复杂度为 O ( n ) O(\sqrt n) O(n )

例题

约束研究 | 洛谷 P1403

  • [AHOI2005]约数研究
    ∑ n i = 1 d ( i ) \underset{i=1}{\overset{n}{\sum}}d(i) i=1nd(i)
    1 ≤ n ≤ 1 0 6 1\le n\le 10^6 1n106,其中 d ( n ) d(n) d(n) 表示 n n n 的约数个数。

  • 稍微想一下,枚举每一个数的约数的个数很复杂,那我们直接枚举每一个约数,考虑每个约数的贡献次数。
    约数范围 1 ∼ n 1\sim n 1n,对于某一个约数 i i i ,共有 ⌊ n i ⌋ \lfloor\frac{n}{i}\rfloor in 个数字有该约数。
    式子直接转化为 ∑ n i = 1 ⌊ n i ⌋ \underset{i=1}{\overset{n}{\sum}}\lfloor\frac{n}{i}\rfloor i=1nin
    注意到,我们可以做 n ≤ 1 0 14 n\le 10^{14} n1014 的数据范围。
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
ll solve(ll x){
    ll L = 1,R = 0;
    ll res = 0;
    while(L <= x){
        R = x/(x/L);
        res += (x / L) * (R - L + 1);
        L = R + 1;
    }
    return res;
}

int main()
{
    ll n;cin >> n;
    cout << solve(n);
    return 0;
}

约数和 | 洛谷 P2424

  • 约数和 | 洛谷 P2424
    给你正整数 a 、 b a、b ab ,求 ∑ b i = a σ ( i ) \underset{i=a}{\overset{b}{\sum}}\sigma(i) i=abσ(i)
    1 ≤ a ≤ b ≤ 2 × 1 0 9 1\le a\le b\le 2\times10^9 1ab2×109,其中 σ ( n ) \sigma(n) σ(n) 表示 n n n 的约数和。

  • 容易想到,答案即为 ∑ b i = 1 σ ( i ) − ∑ a − 1 i = 1 σ ( i ) \underset{i=1}{\overset{b}{\sum}}\sigma(i)-\underset{i=1}{\overset{a-1}{\sum}}\sigma(i) i=1bσ(i)i=1a1σ(i)
    我们要求 ∑ n i = 1 σ ( i ) \underset{i=1}{\overset{n}{\sum}}\sigma(i) i=1nσ(i),枚举每一个约数,计算答案,式子转化为
    ∑ n i = 1 i × ⌊ n i ⌋ \underset{i=1}{\overset{n}{\sum}}i\times \lfloor\frac{n}{i} \rfloor i=1ni×in
    对于同一段,计算贡献为 L × ( L + 1 ) × ⋯ × R × ⌊ n L ⌋ = ⌊ n L ⌋ × ( L + R ) × ( R − L + 1 ) 2 L\times(L+1)\times\cdots\times R\times \lfloor\frac{n}{L}\rfloor=\lfloor\frac{n}{L}\rfloor\times\frac{(L+R)\times(R-L+1)}{2} L×(L+1)××R×Ln=Ln×2(L+R)×(RL+1)
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
ll solve(ll n){
    ll L = 1,R = 0;
    ll res = 0;
    while(L <= n){
        R = n/(n/L);
        res += n / L * (L + R) * (R - L + 1) / 2;
        L = R + 1;
    }
    return res;
}

int main()
{
    ll a,b;cin >> a >> b;
    cout << solve(b) - solve(a-1);
    return 0;
}

余数求和 | 洛谷 P2261

  • 余数求和 | 洛谷 P2261
    给你正整数 n 、 k n、k nk,求 ∑ n i = 1   k   %   i \underset{i=1}{\overset{n}{\sum}}\ k\ \%\ i i=1n k % i
    1 ≤ n , k ≤ 1 0 9 1\le n,k\le 10^9 1nk109

  • 取模符号?我们化简一下式子
    ∑ n i = 1   k   %   i = ∑ n i = 1 k − i ⌊ k i ⌋ = n k − ∑ n i = 1 i ⌊ k i ⌋ \underset{i=1}{\overset{n}{\sum}}\ k\ \%\ i=\underset{i=1}{\overset{n}{\sum}}k-i\lfloor\frac{k}{i}\rfloor=nk-\underset{i=1}{\overset{n}{\sum}}i\lfloor\frac{k}{i}\rfloor i=1n k % i=i=1nkiik=nki=1niik
    看到,右半部分类似一个整除分块。不同的是,分子部分为 k k k 而不是 n n n
    考虑到 ∀ x > k , 都 有 ⌊ k x ⌋ = 0 \forall x>k,都有\lfloor\frac{k}{x}\rfloor=0 x>kxk=0
    并且我们每次枚举,要保证右端点 R R R 不超过 n n n
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
ll solve(ll n,ll k){
    ll L = 1,R = 0;
    ll res = n * k;
    while(L <= min(n,k)){
        R = min(n,k/(k/L));
        res -= (k / L) * (L + R) * (R - L + 1) / 2;
        L = R + 1;
    }
    return res;
}

int main()
{
    ll n,k;cin >> n >> k;
    cout << solve(n,k);
    return 0;
}

⋆ ^\star 扩展 ⌈ \lceil 二维数论分块 ⌋ \rfloor

  • ∑ min ⁡ ( n , m ) i = 1 ⌊ n i ⌋ × ⌊ m i ⌋ \underset{i=1}{\overset{\min(n,m)}{\sum}}\lfloor\frac{n}{i}\rfloor\times\lfloor\frac{m}{i}\rfloor i=1min(n,m)in×im
  • 思路:类似于一维,只不过二维的块数变为 2 m + 2 n 2\sqrt m+2\sqrt n 2m +2n 块。
    每一个小块里面, ⌊ n i ⌋ × ⌊ m i ⌋ \lfloor\frac{n}{i}\rfloor\times\lfloor\frac{m}{i}\rfloor in×im 的值都相同。
    我们对于一个 L L L ,最大的 R R R 怎么找呢?易得为 min ⁡ { ⌊ n ⌊ n L ⌋ ⌋ , ⌊ m ⌊ m L ⌋ ⌋ } \min\{\lfloor\frac{n}{\lfloor\frac{n}{L}\rfloor}\rfloor,\lfloor\frac{m}{\lfloor\frac{m}{L}\rfloor}\rfloor\} min{Lnn,Lmm}
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
ll solve(ll n,ll m){
    ll L = 1,R = 0;
    ll res = 0;
    while(L <= min(n,m)){
        R = min(m/(m/L),n/(n/L));
        res += (n/L) * (m/L) * (R - L + 1);
        L = R + 1;
    }
    return res;
}

模积和 | [清华集训2012] | 洛谷 P 2260

  • ∑ n i = 1 ∑ m j = 1 ( n   m o d   i ) × ( m   m o d   j ) , i ≠ j \underset{i=1}{\overset{n}{\sum}}\underset{j=1}{\overset{m}{\sum}}(n\bmod i)\times(m\bmod j),i\ne j i=1nj=1m(nmodi)×(mmodj)i=j
  • 思路:不要忘记 i ≠ j i\ne j i=j 啊!
    我们开始推狮子:
    I = ∑ n i = 1 ∑ m j = 1 ( n − ⌊ n i ⌋ i ) × ( m − ⌊ m j ⌋ j ) − ∑ min ⁡ ( n , m ) i = 1 ( n − ⌊ n i ⌋ i ) × ( m − ⌊ m i ⌋ i ) = ∑ n i = 1 ( n − ⌊ n i ⌋ i ) ∑ m j = 1 ( m − ⌊ m j ⌋ j ) − ∑ min ⁡ ( n , m ) i = 1 ( n m − n ⌊ m i ⌋ i − m ⌊ n i ⌋ i + ⌊ m i ⌋ ⌊ n i ⌋ i 2 ) = ( n 2 − ∑ n i = 1 ⌊ n i ⌋ i ) ( m 2 − ∑ m j = 1 ⌊ m j ⌋ j ) − min ⁡ ( n , m ) × n m + n ∑ min ⁡ ( n , m ) i = 1 ⌊ m i ⌋ i + m ∑ min ⁡ ( n , m ) i = 1 ⌊ n i ⌋ i − ∑ min ⁡ ( n , m ) i = 1 ⌊ n i ⌋ ⌊ m i ⌋ i 2 \begin{aligned} I&=\underset{i=1}{\overset{n}{\sum}}\underset{j=1}{\overset{m}{\sum}}(n-\lfloor\frac{n}{i}\rfloor i)\times(m-\lfloor\frac{m}{j}\rfloor j)-\underset{i=1}{\overset{\min(n,m)}{\sum}}(n-\lfloor\frac{n}{i}\rfloor i)\times(m-\lfloor\frac{m}{i}\rfloor i)\\ &=\underset{i=1}{\overset{n}{\sum}}(n-\lfloor\frac{n}{i}\rfloor i)\underset{j=1}{\overset{m}{\sum}}(m-\lfloor\frac{m}{j}\rfloor j)-\underset{i=1}{\overset{\min(n,m)}{\sum}}(nm-n\lfloor\frac{m}{i}\rfloor i-m\lfloor\frac{n}{i}\rfloor i+\lfloor\frac{m}{i}\rfloor\lfloor\frac{n}{i}\rfloor i^2)\\ &=(n^2-\underset{i=1}{\overset{n}{\sum}}\lfloor\frac{n}{i}\rfloor i)(m^2-\underset{j=1}{\overset{m}{\sum}}\lfloor\frac{m}{j}\rfloor j)-\min(n,m)\times nm+n\underset{i=1}{\overset{\min(n,m)}{\sum}}\lfloor\frac{m}{i}\rfloor i+m\underset{i=1}{\overset{\min(n,m)}{\sum}}\lfloor\frac{n}{i}\rfloor i-\underset{i=1}{\overset{\min(n,m)}{\sum}}\lfloor\frac{n}{i}\rfloor\lfloor\frac{m}{i}\rfloor i^2 \end{aligned} I=i=1nj=1m(nini)×(mjmj)i=1min(n,m)(nini)×(mimi)=i=1n(nini)j=1m(mjmj)i=1min(n,m)(nmnimimini+imini2)=(n2i=1nini)(m2j=1mjmj)min(n,m)×nm+ni=1min(n,m)imi+mi=1min(n,m)inii=1min(n,m)inimi2
    好家伙,清华集训的题就是不一样。。。
    注意:模数 19940417 = 7 × 2848631 19940417=7\times2848631 19940417=7×2848631不是质数!用拓欧或者欧拉定理求出逆元!
    我们使用函数封装一下,即可优雅 地敲出AC代码
    时间复杂度: O ( max ⁡ ( n , m ) ) O(\sqrt{\max(n,m)}) O(max(n,m) )
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
const ll  MOD = 19940417;

ll ex_gcd(ll a,ll b,ll& x,ll& y)
{
    if(b==0)
    {
        x=1;y=0;
        return a;
    }
    ll ans=ex_gcd(b,a%b,x,y);
    ll tmp=x;
    x=y;
    y=tmp-a/b*y;
    return ans;
}
ll iv(ll a,ll mod)//存在逆元条件:gcd(a,mod)=1
{
    ll x,y;
    ll g=ex_gcd(a,mod,x,y);
    if(g!=1)return -1;
    return (x % mod + mod) % mod;
}

///	数论分块,项数为 n ,分子为 k
ll solve(ll n,ll k){
    ll L = 1,R = 0;
    ll res = 0;
    while(L <= min(n,k)){
        R = min(n,k/(k/L));
        res = (res + k / L % MOD * (L + R) % MOD * (R - L + 1) % MOD * iv(2,MOD) % MOD);
        L = R + 1;
    }
    res = (res % MOD + MOD) % MOD;
    return res;
}
/// 快速算 1^2 + 2^2 + …… + x^2
ll pre(ll x){
    ll res = x * (x + 1) % MOD * (2 * x + 1) % MOD * iv(6,MOD) % MOD;
    return res;
}
///	二维数论分块,项数为 n ,一项分子为 a,另一项分子为 b
ll solve2(ll n,ll a,ll b){
    ll L = 1,R = 0;
    ll res = 0;
    while(L <= n){
        R = min(a/(a/L),b/(b/L));
        res = (res + (a / L) * (b / L) % MOD * (pre(R) - pre(L - 1) % MOD)) % MOD;	/// 注意 i^2 快速求和
        L = R + 1;
    }
    res = (res % MOD + MOD) % MOD;
    return res;
}
int main()
{
    ll n,m;
    cin >> n >> m;
    ll ans = (n * n % MOD - solve(n,n)) * (m * m % MOD - solve(m,m)) % MOD;
    ans = (ans - min(n,m) * n % MOD * m % MOD + n * solve(min(n,m),m) % MOD + m * solve(min(n,m),n) % MOD) % MOD;
    ans = (ans - solve2(min(n,m),n,m)) % MOD;
    ans = (ans + MOD) % MOD;
    cout << ans;
    return 0;
}

⋆ ^\star 扩展 ⌈ \lceil 数论分块变形 ⌋ \rfloor

  • ∑ n i = 1 ⌈ n i ⌉ \underset{i=1}{\overset{n}{\sum}}\lceil\frac{n}{i}\rceil i=1nin
    其中 ⌈ x ⌉ \lceil x\rceil x 表示 x x x 向上取整。
  • 思路:有一个转化:对于正整数 a 、 b a、b ab,我们有 ⌈ a b ⌉ = ⌊ a + b − 1 b ⌋ \lceil\frac{a}{b}\rceil=\lfloor\frac{a+b-1}{b}\rfloor ba=ba+b1
    证明:设 ⌈ a b ⌉ = ⌊ a + b − 1 b ⌋ = x \lceil\frac{a}{b}\rceil=\lfloor\frac{a+b-1}{b}\rfloor=x ba=ba+b1=x
    因为 ⌈ a b ⌉ = x \lceil\frac{a}{b}\rceil=x ba=x,我们得出 a b ∈ ( x − 1 , x ] \frac{a}{b}\in(x-1,x] ba(x1,x]
    又因为 b b b 是正整数,所以 b − 1 b ∈ [ 0 , 1 ) \frac{b-1}{b}\in[0,1) bb1[0,1)
    所以 a b + b − 1 b ∈ ( x − 1 , x + 1 ) \frac{a}{b}+\frac{b-1}{b}\in(x-1,x+1) ba+bb1(x1,x+1)
    所以 ⌊ a + b − 1 b ⌋ = x \lfloor\frac{a+b-1}{b}\rfloor=x ba+b1=x
    所以 ⌈ a b ⌉ = ⌊ a + b − 1 b ⌋ □ \lceil\frac{a}{b}\rceil=\lfloor\frac{a+b-1}{b}\rfloor\qquad\Box ba=ba+b1
  • 代替原式子,得到 ∑ n i = 1 ⌈ n i ⌉ = ∑ n i = 1 ⌊ n + i − 1 i ⌋ = ∑ n i = 1 ( ⌊ n − 1 i ⌋ + 1 ) \underset{i=1}{\overset{n}{\sum}}\lceil\frac{n}{i}\rceil=\underset{i=1}{\overset{n}{\sum}}\lfloor\frac{n+i-1}{i}\rfloor=\underset{i=1}{\overset{n}{\sum}}(\lfloor\frac{n-1}{i}\rfloor+1) i=1nin=i=1nin+i1=i=1n(in1+1)
    这个应该不用代码了吧,和上述代码同理请仔细思考哦。
  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值