2017.3.19 約數個數和 失敗總結
這個題一眼上去應該不是很難,但除了求約數個數有個分解質因數的技巧之外就不會什麼了;;
本來想著預處理50000以內的約數個數和之後求個字首和直接乘、、、可見多麼蠢、、搞了2h後棄療了
結果題解是 要算gcd (。_。)
還有莫比烏斯反演、、、 …(⊙_⊙;)…
好吧,先借機學一下莫比烏斯反演 、、
其實這道題幾乎就是為莫比烏斯而生的(板子題)
似乎是函式的反函式、、
所以叫反演、、 (*゜ー゜*)
而這個μ函式就是傳說中的的莫比烏斯函式;
要注意是F(n/d)
那這個要怎麼用?
那此題來說,首先要證明這個式子:
為什麼會和gcd有關?
若i和j互質,那麼i*j就是對於d(mn)唯一的約數
如果不互質,那麼i和j還能再拆出幾個數,作為約數
如 i=4 j=6,n=8,j=12;;
i=2*2 j=2*3
那麼 i*j=2*2*2*3
可以寫出互質的8 * 3
在迴圈到i=8,j=3時會被算進去、
而這些拆出的數一定可以對答案貢獻1
所以用了bool;;
然後原式相當於求1~n、1~m的和:
根據 sigma交換律 和 可以處理處的某個積性函式μ可以得出莫比烏斯反演,:
(就是套公式, 【gcd(a,b)==1】= Σ(d|i,d|j)μ(d) )
注意μ是根據d能否整除i、d能否整除j 對d的一個函式,返回數值
有人可能要問gcd的判斷和 i、j、d的整除關係得到的函式有什麼關係?
注意多了一個Σ:也就是說它是先用前面四坨Σ枚舉了1~n、 1~m所有可能的約數、、而這最後一個Σ則是列舉約數的約數、、(當然,預設N<M)
若d既整除了i,又整除了j,則i和j不互質,它有公因數d、、、
然後就不會了
我們注意到會有很多重複,重複的個數為 下取整(N/i) 所以可以用乘法優化加法,幹掉兩個Σ、、
這是反演的一步通用優化
再從d(約數)的角度交換一下位置:
對於每個d都要乘每種可能的i、j搭配,但對於n、m只會出現n/d、m/d次,每次搭配又會產生n/(i*d)、 m/(j*d)個重複
所以都可以乘起來再加
把最右邊兩項的d寫到分子:
我們可以發現有兩個不相關的項,可以獨立處理:
而且這兩個函式計算方式一毛一樣,只是定義域值域不一樣所以:
就可以化為:
分別處理f函式,再用線篩求出μ函式,就可以做了;;;
碼:
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
int mu[50001];
ll f[50001],ans;
int i,j,n,tot,su[50001],k,t,m;
bool vis[50001];
void eular()
{
mu[1]=1;
for(int i=2;i<=50000;i++)
{
if(!vis[i])
{
su[++tot]=i;
mu[i]=-1;//質數統一為 -1
}
for(j=1;i*su[j]<50000&&j<=tot;j++)
{
vis[i*su[j]]=1;
if(i%su[j])
{
mu[i*su[j]]=-mu[i];//合數如果沒有>2的次方的質數就看元素個數(取反,即為(-1)^n)
}else
{
mu[i*su[j]]=0;//合數如果是p^n形式,mu就是0
break;
}
}
}
for (i=2; i<=50000; i++) mu[i]+=mu[i-1]; //處理字首和(因為題目有Σ)
for (i=1; i<=50000; i++) //計算f
for (j=1; j<=i; j=k+1){
k=i/(i/j); //可能有點mengbi,k存的是最大的i/j,同 餘數之和sum一題
f[i]+=(ll)(k-j+1)*(i/j); //用乘法加速連加
}
}
int main()
{ scanf("%d",&t); eular();
while(t--)
{
scanf("%d%d",&n,&m);
ans=0;
for (i=1; i<=m && i<=n; i=j+1){//列舉p,計算貢獻
j=min(m/(m/i),n/(n/i)); //同餘數之和sum的計算方式
ans+=f[m/i]*f[n/i]*(mu[j]-mu[i-1]);//用公式+字首和統計答案
}
if(t)printf("%lld\n",ans);
else printf("%lld",ans);
}
}
莫比烏斯反演就是一個渠道,可以通過這個渠道對看似沒有辦法化簡的式子轉化為若干子函式,這樣複雜度就會降低、
但式子的轉化極其靈活,必須確保每一步正確性,
可以說,能用莫比烏斯反演的題都需要極強的數學敏感度和縝密的推理
當然,暴力價效比高、
附一張反演函式計演算法則: