上模板题例题:
[CQOI2007]余数求和
题目大意:求 $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; }
另外再推荐几题。抱歉只找到一题,虽说也不错