zoukankan      html  css  js  c++  java
  • 整除分块学习笔记+[CQOI2007]余数求和(洛谷P2261,BZOJ1257)

    模板题例题:

    [CQOI2007]余数求和

    洛谷

    BZOJ

    题目大意:求 $sum^n_{i=1}k mod i$ 的值。

    等等……这题就学了三天C++的都会吧?

    $1leq n,kleq 10^9$。(一口老血喷到屏幕上)

    $O(n)$ 行不通了,考虑别的做法。


    我们来看一下 $lfloorfrac{x}{i} floor$ 的值。

    $x=9$:(不包括0,只有4种取值?)

    i

    1 2 3 4 5 6 7 8 9 10
    x/i 9 4 3 2 1 1 1 1 1

    0

    $x=12$:(不包括0,只有6种取值?)

    i 1 2 3 4 5 6 7 8 9 10 11 12
    x/i 12 6 4 3 2 2 1 1 1 1 1

    1

    貌似 $lfloorfrac{x}{i} floor$ 取值数不是很多?

    我们来估算一下 $lfloorfrac{x}{i} floor$ 的不同取值个数:

    当 $1leq ileq lfloorsqrt{x} floor$ 时,$i$ 都只有 $lfloorsqrt{x} floor$ 个,不同的取值数肯定不会更多。

    当 $lfloorsqrt{x} floorleq ileq x$ 时,$1leqlfloorfrac{x}{i} floorleqlfloorsqrt{x} floor$,不同的取值数肯定 $leqlfloorsqrt{x} floor$ 个。

    综上,不同取值数是 $sqrt{x}$ 级别的。

    然后我们可以发现相同的数是连续的一段。那么我们可以通过这个特点把 $lfloorfrac{x}{i} floor$ 分成几段,每一段的数相等,那么这一段的和就是长度 $ imes$ 这个相同的数。

    因为不同取值只有 $sqrt{x}$ 个,所以这样加速后的时间复杂度是 $O(sqrt{x})$,比 $O(x)$ 快了不少。这就是整除分块。


    回到原题。

     求 $sum^n_{i=1}k mod i$ 的值。

    这个……看着和整除分块没什么大关系的样子?

    我们看这个 $mod$ 真碍眼,把它拆开。

    $k mod i=k-i imeslfloorfrac{k}{i} floor$

    那么就有:

    $ sum^n_{i=1}k mod i$

    $=sum^n_{i=1}k-i imeslfloorfrac{k}{i} floor$

    $=nk-sum^n_{i=1}i imeslfloorfrac{k}{i} floor$

    后面这个式子貌似可以整除分块了……怎么算呢?

    我们考虑 $[l,r]$ 这段区间的求和,其中 $lfloorfrac{k}{i} floor=x:iin [l,r]$。

    $ sum^r_{i=l}i imeslfloorfrac{k}{i} floor$

    $=sum^r_{i=l}i imes x$

    $=xsum^r_{i=l}i$

    $=frac{x(l+r)(r-l+1)}{2}$

    这样就不是很难了。


    话说讲了这么久也没讲怎么枚举一段相同区间的左端点和右端点。

    我们这样扫描:

    一开始 $l=1$ 显而易见。

    求出对应的 $r$。

    这个区间求完了,下一个 $l$ 应该是下一个还没扫过的位置,即 $l=r+1$。

    一直重复直到 $l$ 到了上界,也就是扫完了。

    怎么求对应的 $r$ 呢?

    既然 $lfloorfrac{k}{l} floor=lfloorfrac{k}{r} floor$,且 $r$ 是右端点(最大)

    那么 $r=frac{k}{frac{k}{l}}$。(当然可能要跟枚举上界取一个min,视情况而定)

    整除分块模板大概如下:

    1 for(int l=1,r;l<=n;l=r+1){
    2     r=n/(n/l);
    3     //do something...
    4 }

    那么这题代码实现就不难了。需要注意本题有不少坑点,详见代码。(没错,代码并没有你想象的那么长!)

    时间复杂度貌似是 $O(sqrt{min(k,n)})$,空间复杂度 $O(1)$

    #include<iostream>
    #include<cmath>
    using namespace std;
    typedef long long ll;    //long long是需要的
    ll n,k,ans;
    int main(){
        cin>>n>>k;
        ans=n*k;
        for(ll l=1,r;l<=min(n,k);l=r+1){    //与上界取min!
            r=min(k/(k/l),n);    //与上界取min!
            ans-=(k/l)*(l+r)*(r-l+1)/2;
        }
        cout<<ans<<endl;
    }
    整除分块的超简短代码

    另外再推荐几题。抱歉只找到一题,虽说也不错

     洛谷P3935 Calculating 题解戳我

  • 相关阅读:
    list()
    Python 数据类型转换
    设计模式 — 代理模式(静态代理、动态代理、Cglib代理) 转载
    java线程池实现原理
    HashMap深度解析(转载) jdk1.7
    Java Serializable 序列化和反序列化 (转载)
    Java遍历HashMap并修改(remove)(转载)
    Java中的break,continue关于标签的用法(转载)
    遍历List过程中删除操作报java.util.ConcurrentModificationException错误
    java Date时间的各种转换方式和Mysql存时间类型字段的分析
  • 原文地址:https://www.cnblogs.com/1000Suns/p/9193713.html
Copyright © 2011-2022 走看看