整除分塊學習筆記+[CQOI2007]余數求和(洛谷P2261,BZOJ1257)
上模板題例題:
[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)