1. 程式人生 > 其它 >整除分塊入門(更新中)

整除分塊入門(更新中)

【正文】

整除分塊是個什麼東西,他是一種用來高效求
\(\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\)

就是最大的 $ i$ 滿足 \(ik\leq n\) ,此時的 \(i\) 也就是我們所求的 \(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;
}

先寫一點,後面還有挺多例題與擴充套件,可以水好多藍題與紫題