1. 程式人生 > >2017.3.19 約數個數和 失敗總結

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);
}
}


莫比烏斯反演就是一個渠道,可以通過這個渠道對看似沒有辦法化簡的式子轉化為若干子函式,這樣複雜度就會降低、

但式子的轉化極其靈活,必須確保每一步正確性,

可以說,能用莫比烏斯反演的題都需要極強的數學敏感度和縝密的推理

當然,暴力價效比高、

附一張反演函式計演算法則: