1. 程式人生 > >整除分塊學習筆記+[CQOI2007]余數求和(洛谷P2261,BZOJ1257)

整除分塊學習筆記+[CQOI2007]余數求和(洛谷P2261,BZOJ1257)

CQ 找到 SQ 等等 HP alt target new n)

模板題例題:

[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 (題解待填充)

整除分塊學習筆記+[CQOI2007]余數求和(洛谷P2261,BZOJ1257)