前置:整除分塊
- 主要形式就是:
這個式子正常是 \(\Theta(n)\) 的效率,但是我們還可以縮小成 \(\Theta(\sqrt{n})\)。
- 對於每一個 \(\lfloor\frac{n}{i}\rfloor\) , 易得
(打表)有許多的 \(\lfloor\frac{n}{i}\rfloor\) 是一樣的(廢話)。我們就可以根據它們的分佈情況進行計算。
可以發現,對於每個相同值的一塊,最後一個數是 \(\frac{n}{\frac{n}{i}}\) ,然後就可以 \(\Theta(\sqrt{n})\) 處理了。
程式碼
for(int l=1,r;l<=n;l=r+1){
r=n/(n/l);
ans+=(r-l+1)*(n/l);
}
擴充
- 有時候,可能推出來的式子不一定就是一個很裸的整除分塊,可能會與某些積性函式相乘,如: \(\mu,\phi\) ...... 這時候,每當我們使用整除分塊跳過一個區間的時候,其所對應的函式值也跳過了一個區間。所以此時,就需要乘上那一個區間的函式值,利用字首和記錄即可。
莫比烏斯函式
定義
\(\mu (d)\) 為莫比烏斯函式的函式名,是一個由容斥係數所構成的函式。其定義為:
1、當 \(d = 1\) 時, \(\mu (d)=1\) 。
2、當 \(d = \prod_{i-1}^k\ p_i\) ,並且所有的 \(p_i\) 為不同的素數的時候, \(\mu(d)=(-1)^k\) 。
3、當 \(d\) 含有的任一質因子的冪大於 \(2\) 次,那麼 \(\mu(d)=0\) 。
性質
1、最常用:對於任意正整數 \(n\) ,\(\sum_{d|n}\mu(d)=[n=1]\) 。其中 \([\ ]\) 代表的是布林型,即只有 \(n=1\) 成立的時候才為 \(1\) ,其他情況為 \(0\) 。(由 \(\mu\) 的容斥係數的性質可證明。PS:我不會。)
2、對於任意正整數 \(n\) ,
具體的證明見部落格:莫比烏斯反演
程式碼實現
任何函式在OI中都是需要用到值的,那麼我們就需要一些篩法,其中 \(xxs\) (線性篩)是最簡單的一種,線上性篩素數的基礎上稍做修改即可。
void xxs(){
mu[1] = 1;
for(int i = 2;i <= n;++i){
if(!noprime[i]){
prime[++prime[0]] = i;
mu[i] = -1;
}
for(int j = 1,k;j <= prime[0] && (k = i * prime[j]) <= n;++j){
noprime[k] = 1;
if(i % prime[j] == 0)break;
else mu[k] = -mu[i];
}
}
}
莫比烏斯反演
有了 \(\mu\) 函式的基礎,我們就可以看莫比烏斯繁衍反演了。
定理
定義 \(F(n)\) 和 \(f(n)\) 是定義在非負整數集合上的兩個函式,並且滿足條件:
那麼一定存在:
此定理即為莫比烏斯定理。
證明
通過定義證明:
由 \(F(n)\) 和 \(f(n)\) 的定義可得:
那麼
- 另一個式子:
當 \(F(n)\) 和 \(f(n)\) 滿足:
得到:
例題1
YY的GCD
莫比烏斯反演的開始。
首先記錄一個套路:在 \(gcd\) 問題中,通常把反演中的 \(f(d)\) 設為 \(gcd(i,j)=d\)的個數, \(F(n)\) 設為 \(\sum_{base=1}^{d\times base \leqslant n} [gcd(i,j)=d\times base]\) ,即 \(gcd(i,j)=d\) 和 \(d\) 的倍數的個數。
在這個題中,我們不這樣定義(因為不好證),這道題要求的是
所以樸素演算法就是,我們可以列舉每一個質數,進行求和,也就是這樣:
我們直接把 \(k\) 在後兩個式子中除去,得到:
然後開始繁衍反演。
由於只有在 \(gcd(i,j)=1\) 時才有貢獻,那麼我們就可以根據第一個莫比烏斯函式的性質(忘了往上翻)把最後一個 \(gcd(i,j)=1\) 進行轉換,變為:
那麼在這裡, \(d\) 一定是 \(gcd(i,j)\) 的倍數,所以我們就可以繼續化簡,在 \(i\),\(j\) 的極限值上同時除以一個 \(d\) ,直接乘在式子裡即可,然後列舉 \(d\)。得到:
這樣的時間複雜度還是不對的,因為最大的 \(n,m\) 是 \(10^7\),所以我們就可以(不得不)繼續化簡。
設 \(tmp=kd\) ,那麼原式子就變成了:
我們再對這個式子化簡,把 \(tmp\) 提前列舉,那麼我們就得到了最終的式子:
最後這個式子我們可以線上性篩的時候用迪利克雷字首和來搞一個字首和,然後在下邊只需要用整除分塊的思想列舉 \(tmp\) ,每一塊的和就能求出來了。
程式碼
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e7+10;
int mu[maxn],noprime[maxn],prime[maxn];
int sum[maxn],f[maxn];
int n;
void xxs(){
mu[1] = 1;
noprime[1] = 1;
for(int i = 2;i <= 10000000;++i){
if(!noprime[i]){
prime[++prime[0]] = i;
mu[i] = -1;
}
for(int j = 1,k;j <= prime[0] && (k = i * prime[j]) <= 10000000;++j){
noprime[k] = 1;
if(i % prime[j] == 0)break;
else mu[k] = -mu[i];
}
}
for(int i = 1;i <= prime[0];++i){
for(int j = 1;j * prime[i] <= 10000000;++j){
f[j * prime[i]] += mu[j];
}
}
for(int i = 1;i <= 10000000;++i){
sum[i] = sum[i-1] + f[i];
}
}
#define ll long long
int main(){
xxs();
int T;
scanf("%d",&T);
while(T--){
int n,m;
scanf("%d%d",&n,&m);
if(n > m)swap(n,m);
long long ans = 0;
for(int l = 1,r = 0;l <= n;l = r + 1){
r = min(n / (n / l),m / (m / l));
ans += (ll)(sum[r] - sum[l-1]) * (ll)(n / l) * (ll)(m / l);
}
printf("%lld\n",ans);
}
return 0;
}
莫比烏斯反演的一些知識就到這裡,其考察的一些題都是一些推柿子,所以需要慢慢鑽研。篩法以後可能會更新(那得看我會不會退役),持續更新(咕咕咕)。
\(Never\ Give\ Up\)