整除分块学习笔记+[CQOI2007]余数求和(洛谷P2261,BZOJP1257)

模板题例题:

[CQOI2007]余数求和

洛谷

BZOJ

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

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

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

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


我们来看一下 $\lfloor\frac{x}{i}\rfloor$ 的值。

$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

貌似 $\lfloor\frac{x}{i}\rfloor$ 取值数不是很多?

我们来估算一下 $\lfloor\frac{x}{i}\rfloor$ 的不同取值个数:

当 $1\leq i\leq \lfloor\sqrt{x}\rfloor$ 时,$i$ 都只有 $\lfloor\sqrt{x}\rfloor$ 个,不同的取值数肯定不会更多。

当 $\lfloor\sqrt{x}\rfloor\leq i\leq x$ 时,$1\leq\lfloor\frac{x}{i}\rfloor\leq\lfloor\sqrt{x}\rfloor$,不同的取值数肯定 $\leq\lfloor\sqrt{x}\rfloor$ 个。

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

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

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


回到原题。

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

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

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

$k\ mod\ i=k-i\times\lfloor\frac{k}{i}\rfloor$

那么就有:

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

$=\sum^n_{i=1}k-i\times\lfloor\frac{k}{i}\rfloor$

$=nk-\sum^n_{i=1}i\times\lfloor\frac{k}{i}\rfloor$

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

我们考虑 $[l,r]$ 这段区间的求和,其中 $\lfloor\frac{k}{i}\rfloor=x:i\in [l,r]$。

$\ \sum^r_{i=l}i\times\lfloor\frac{k}{i}\rfloor$

$=\sum^r_{i=l}i\times x$

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

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

这样就不是很难了。


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

我们这样扫描:

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

求出对应的 $r$。

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

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

怎么求对应的 $r$ 呢?

既然 $\lfloor\frac{k}{l}\rfloor=\lfloor\frac{k}{r}\rfloor$,且 $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 (题解待填充)

猜你喜欢

转载自www.cnblogs.com/1000Suns/p/9193713.html