整除分塊入門(更新中)
【正文】
整除分塊是個什麼東西,他是一種用來高效求
\(\sum_{i=1}^n \lfloor \dfrac{n}{i} \rfloor\) 的方法。這個東西在莫比烏斯反演中會經常遇到,所以還是比較重要。學了這個東西你可以馬上去學莫比烏斯反演。
運用整除分塊我們可以高效的解決這個問題,時間複雜度為 \(\sqrt{n}\) 。
先舉個例子吧,假設 \(n =25\) 。
那麼式子所有的情況可以列出來如下: (下面表來源於 \(\text{cmd}\) 哥哥)
################################################## 25/1=25 * ######################## 25/2=12 * ################ 25/3=8 * ############ 25/4=6 * ########## 25/5=5 * ######## 25/6=4 * ###### 25/7=3 * ###### 25/8=3 #### 25/9=2 * #### 25/10=2 #### 25/11=2 #### 25/12=2 ## 25/13=1 * ## 25/14=1 ## 25/15=1 ## 25/16=1 ## 25/17=1 ## 25/18=1 ## 25/19=1 ## 25/20=1 ## 25/21=1 ## 25/22=1 ## 25/23=1 ## 25/24=1 ## 25/25=1
按照取值分類發現有九種取值,把每一種取值看成一塊,那麼就有九塊。
################################################## 25 ---------- ######################## 12 ---------- ################ 8 ---------- ############ 6 ---------- ########## 5 ---------- ######## 4 ---------- ###### 3*2 ###### ---------- #### 2*4 ... #### ---------- ## 1*12 ... ## ----------
那麼這麼看起來,如果我們知道了可能的取值有幾塊,並且知道了每一塊的塊長,那麼我們就可以很快的求得最終式子的答案。
假設,我們現在已經知道了一個塊的左端點為 \(l\) ,且該塊的值為 \(k\) 。如果我們求出了右端點 \(r\)。
那麼這一塊的貢獻就為 \((r-l+1) \times k\)
考慮怎麼求右端點 \(r\)
可以發現對於這個塊中的元素,也即是 \(l \sim r\)間的元素。因為他們的值都為 \(k\) ,所以我們也就是說下面這個式子:
\[\lfloor \dfrac{n}{i} \rfloor = \lfloor \dfrac{n}{l} \rfloor \quad i \in[l,r] \]那麼我們知道我們所求的 \(r\)
那麼就是如下式子:
\[ \left\{ \begin{aligned} k & = \ \lfloor \dfrac{n}{l} \rfloor\\ r & = \ max(i),ik \leq n \\ \end{aligned} \right. \]也就是說:
\(r= \lfloor \dfrac{n}{k} \rfloor = \lfloor \dfrac{n}{ \dfrac{n} {l}} \rfloor\)
然後你求完 \(r\) ,那麼 \(r+1\) 就是新的左端點,然後就可以繼續往下求。
根據他人部落格知道總共的塊值 只有 \(\sqrt{n}\) 種取值,又因為我們的時間複雜度是在依賴塊值的。所以,這個東西的複雜度就為 \(\sqrt{n}\) 。
然後就可以做例題1。
例題題解:
首先對於式子 $ k % i$ 可以將其變為 $ k- i \dfrac{k}{i}$
那麼我們要求的式子就變為了 $ \sum_{i=1}^n k- i \lfloor \dfrac{k}{i} \rfloor $ ,發現可以將一些 $ k$ 提取出來。
所以式子就變成了:
\[k \times n- \sum_{i=1}^n i \lfloor \dfrac{k}{i} \rfloor \]我們發現,裡面的 \(\sum_{i=1}^n \lfloor \dfrac{k}{i} \rfloor\) 其實就是我們所講的整除分塊。然後就可以很快的完成計算,但是有個問題是,我們要求的是 \(\sum_{i=1}^n i \lfloor \dfrac{k}{i} \rfloor\) ,答案還需要乘上一個係數 \(i\) 。
容易發現,我們的 \(\lfloor \dfrac{k}{i} \rfloor\) 中在不同的 \(i\) 時的取值是有重複的(上面講了)。
考慮求 \(\lfloor \dfrac{k}{i} \rfloor\) 相等的一塊 \([l,r]\) 的貢獻,也就是 \(\sum_{i=l}^r i \lfloor \dfrac{k}{i} \rfloor\) 。這個式子有一個更好看的形式,因為它的 \(\lfloor \dfrac{k}{i} \rfloor\) 相等。
所以,這一塊的貢獻可以寫為:
\[[(l)+(l+1)+(l+2)+ {...} +(r-1)+(r) ] \times \lfloor \dfrac{k}{i} \rfloor \]容易發現前面是一坨等差數列,然後就可以用等差數列求和公式,或者說預處理字首。但因為這裡 \(n\) 較大,所以需要用公式。
程式碼:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k;
int sum(int l,int r){
return (r-l+1)*(l+r)/2;
}
int calc(int n){
int ans=0;
for(int l=1,r;l<=n&&k/l;l=r+1){//k/l有可能為0,這樣答案貢獻為0,所以不用管
r=min(k/(k/l),n);
ans+=(k/l)*sum(l,r);
}
return ans;
}
signed main(){
cin>>n>>k;
cout<<n*k-calc(n);
return 0;
}
例題2
例題題解
發現,例題其實就是要我們求 \(\sum_{i=1}^bf(i)-\sum_{i=1}^{a-1}f(i)\)
顯然的結論是 \(\sum_{i=1}^n f(i)\) 就是 \(\sum_{i=1}^n i \lfloor \dfrac{n}{i} \rfloor\)
這東西太熟悉了,就是我們上面的那道題的一個子問題,然後就直接整除分塊過。
程式碼
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k;
int sum(int l,int r){
return (r-l+1)*(l+r)/2;
}
int calc(int n){
int ans=0;
for(int l=1,r;l<=n&&k/l;l=r+1){//k/l有可能為0,這樣答案貢獻為0,所以不用管
r=min(k/(k/l),n);
ans+=(k/l)*sum(l,r);
}
return ans;
}
int x,y;
signed main(){
cin>>x>>y;
n=y;k=y;
int ans1=calc(n);
n=x-1;k=x-1;
int ans2=calc(n);
cout<<ans1-ans2;
return 0;
}
先寫一點,後面還有挺多例題與擴充套件,可以水好多藍題與紫題