zoukankan      html  css  js  c++  java
  • 「算法笔记」整除分块

    一、 基本思想

    对于形如 (sum_{i=1}^n f(lfloor frac{n}{i} floor)) 的式子,先不考虑求 (f(lfloor frac{n}{i} floor)) 的复杂度,则若采用朴素的解法,时间复杂度为 (O(n))

    简便起见,我们令 (f(lfloorfrac{n}{i} floor)=lfloor frac{n}{i} floor)

    打表找规律可以发现,(lfloor frac{n}{i} floor) 的取值并没有 (n) 个,且在一定区域内相等,呈块状阶梯分布

    对于每一个块,假设它的左端点为 (l),右端点为 (r),对于 (forall iin[l,r])(lfloorfrac{n}{i} floor) 的值都相等。那么我们只需要求出,每一个块的左端点和右端点,以及块内的值即可。

    二、 具体实现

    Part 1. 块的数量

    对于任意整数 (i) 满足 (1leq ileq n)(lfloorfrac{n}{i} floor) 最多只有 (2sqrt{n}) 个不同的值。因为:

    • (ileq sqrt{n}) 时,(i) 只有 (sqrt{n}) 种选择,故 (lfloorfrac{n}{i} floor) 至多只有 (sqrt{n}) 个不同的值。

    • (i>sqrt{n}) 时,(lfloorfrac{n}{i} floor < sqrt{n}),故 (lfloorfrac{n}{i} floor) 也至多只有 (sqrt{n}) 个不同的值。

    综上所述,对于 (i=1sim n)(lfloorfrac{n}{i} floor < sqrt{n}) 由不超过 (2sqrt{n}) 个块组成。

    Part 2. 块的左右端点

    对于任意一个 (i)(ileq n)),我们需要找到一个最大的 (x)(ileq xleq n)),使得 (lfloorfrac{n}{i} floor=lfloorfrac{n}{x} floor)

    此时 (x=lfloorfrac{n}{lfloorfrac{n}{i} floor} floor)。如下所述:

    • 显然 (xleq n),考虑证明 (xgeq i)
      证明:因为 (lfloorfrac{n}{i} floorleq frac{n}{i}),所以 (lfloorfrac{n}{lfloorfrac{n}{i} floor} floor geq lfloorfrac{n}{frac{n}{i}} floor=lfloor i floor=i)。则 (ileq lfloorfrac{n}{lfloorfrac{n}{i} floor} floor =x)。证毕。

    • 不妨设 (k=lfloorfrac{n}{i} floor),考虑证明当 (lfloorfrac{n}{x} floor=k) 时,(x) 的最大值为 (lfloorfrac{n}{k} floor)
      证明:(lfloorfrac{n}{x} floor=k),等价于 (kleq frac{n}{x}<k+1),那么 (frac{1}{k+1}<frac{x}{n}leq frac{1}{k}),则 (frac{n}{k+1}<xleq frac{n}{k})。又因为 (x) 为整数,所以,(x_{max}=lfloorfrac{n}{k} floor)。证毕。

    每一块 (iin[x,lfloorfrac{n}{lfloor frac{n}{x} floor} floor])(lfloor frac{n}{i} floor) 的值都等于 (lfloorfrac{n}{x} floor)

    对于每一个块,假设它的左端点为 (l),则它的右端点为 (lfloorfrac{n}{lfloorfrac{n}{l} floor} floor),并且块内元素都为 (lfloorfrac{n}{l} floor)

    注意:有时需要考虑 (lfloor frac{k}{l} floor=0) 的情况。

    时间复杂度:(mathcal{O}(sqrt{n}))。

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    int n,ans;
    signed main(){
        scanf("%lld",&n);
        for(int l=1,r=0;l<=n;l=r+1)    //l 为块的左端点,r 为块的右端点 
            r=n/(n/l),ans+=(r-l+1)*(n/l);    //块的大小为 r-l+1,块内元素的值都为 n/l 下取整,则整个块的元素之和为 (r-l+1)*(n/l) 
        printf("%lld
    ",ans);
        return 0;
    }

    Expand. 二维整除分块

    (sum_{i=1}^{min(n,m)} lfloorfrac{n}{i} floor lfloorfrac{m}{i} floor)

    此时可将代码中 r=n/(n/l) 替换成 r=min(n/(n/l),m/(m/l))

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    int n,m,ans;
    signed main(){
        scanf("%lld%lld",&n,&m);
        for(int l=1,r=0;l<=min(n,m);l=r+1){
            r=min(n/(n/l),m/(m/l));
            ans+=(r-l+1)*(n/l)*(m/l);
        }
        printf("%lld
    ",ans);
        return 0;
    }

    三、例题

    1. Luogu P2261 [CQOI2007]余数求和

    题目大意:给出正整数 (n)(k),求 (sum_{i=1}^n k mod i) 的值。(1leq n,kleq 10^9)

    Solution:

    注意到 (kmod i=k-lfloor frac{k}{i} floor imes i),所以 (sum_{i=1}^n k mod i=sum_{i=1}^n k-sum_{i=1}^nlfloor frac{k}{i} floor imes i)(=n imes k-sum_{i=1}^nlfloor frac{k}{i} floor imes i)

    考虑整除分块,对于每一个块,假设它的左端点为 (l),右端点为 (r),则对于 (forall iin[l,r])(lfloor frac{k}{i} floor imes i=lfloor frac{k}{l} floor imes i),直接用等差数列求和公式计算即可。

    注意 (lfloor frac{k}{l} floor=0) 的情况。

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    int n,k,ans;
    signed main(){
        scanf("%lld%lld",&n,&k);
        for(int l=1,r=0;l<=n;l=r+1) 
            r=k/l==0?n:min(k/(k/l),n),ans+=(k/l)*((l+r)*(r-l+1)/2);    //注意这里 k/(k/l) 可能会超过 n,所以需要取 min。 
        printf("%lld
    ",n*k-ans);
        return 0;
    }

    2. Luogu P3935 Calculating

    题目大意:(x) 分解质因数结果为 (x=p_1^{k_1}p_2^{k_2}cdots p_n^{k_n}),令 (f(x)=(k_1+1)(k_2+1)cdots (k_n+1)),求 (sum_{i=l}^rf(i))(998244353) 取模的结果。(1leq lleq 10^{14},1leq rleq 1.6 imes 10^{14},r-l>10^{14})

    Solution:

    根据唯一分解定理,可得:(n=p_1^{c_1} imes p_2^{c_2} imes cdots imes p_k^{c_k}=prodlimits_{i=1}^kp_i^{c_i})

    (p_i^{c_i}) 的约数有 (p_i^0,p_i^1,cdots,p_i^{c_i})(c_i+1) 个,根据乘法原理,可得 (n) 的约数个数为 (d(n)=prodlimits_{i=1}^k (c_i+1))

    容易得出,(f(n)) 实际上就是 (n) 的约数个数。

    (S(n)=sum_{i=1}^n f(i)),则 (sum_{i=l}^r f(i)=S(r)-S(l-1))

    (S(n)=sum_{i=1}^n sum_{dmid i} 1=sum_{d=1}^nlfloor frac{n}{d} floor)。然后就可以用整除分块求了。

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    const int mod=998244353;
    int l,r,ans;
    int query(int n){    //求 S(n) 
        int ans=0;
        for(int l=1,r=0;l<=n;l=r+1)
            r=n/(n/l),ans=(ans+(r-l+1)*(n/l)%mod)%mod;
        return ans;
    }
    signed main(){
        scanf("%lld%lld",&l,&r);
        printf("%lld
    ",(query(r)-query(l-1)+mod)%mod);
        return 0;
    }

    四、习题

    • Luogu P2260 [清华集训2012]模积和
  • 相关阅读:
    牛客代码测试栈深度
    "Coding Interview Guide" -- 在行列都排好序的矩阵中找数
    "Coding Interview Guide" -- 括号字符串的有效性和最长有效长度
    "Coding Interview Guide" -- 将正方形矩阵顺时针转动90°
    "Coding Interview Guide" -- 按照左右半区的方式重新组合单链表
    "Coding Interview Guide" -- 先序、中序和后序数组两两结合重构二叉树
    "Coding Interview Guide" -- 只用位运算不用算术运算实现整数的加减乘除运算
    "Coding Interview Guide" -- 从N个数中等概率打印M个数
    "Coding Interview Guide" -- 判断字符数组中是否所有的字符都只出现过一次
    "Coding Interview Guide" -- 字符串的统计字符串
  • 原文地址:https://www.cnblogs.com/maoyiting/p/14010604.html
Copyright © 2011-2022 走看看