【NOIP入門數論】分解質因數

自創題目詳解

數據包

【數據資源】鏈接:http://pan.baidu.com/s/1eRHCMwq 密碼:9laz

題面

【題目名稱】分解質因數
【時間限制】1000ms
【空間限制】128M
【題目描述】
Pii.
n滿(a,b)
(1)1abn;
(2)ta,b,PtPn.
【輸入格式】
一個正整數n.
【輸出格式】
一個正整數,表示合題意的有序正整數對的數目.

樣例輸入1 樣例說明1
6 在滿足1<=a<=b<=6的條件下,共有21對;除開(5,5)不合題意,其餘均滿足條件。
樣例輸出1
20
樣例輸入2 樣例說明2
7 在滿足1<=a<=b<=7的條件下,共有28對;其中(2,2),(2,4),(2,6),(3,3),(3,6),
樣例輸出2 (4,4),(4,6),(5,5),(6,6)不合題意.
19

【數據範圍】
50%1n1000;
100%1n1000000.
————————————————————————————————————————————————
【題解】
50分算法:
只要聽話,就一定可以拿全50分!我們只需要先將n分解質因數,並將這些質因數都記錄下來。
之後老老實實枚舉a,b再用歐幾里得求出最大公約數,直接分解質因數驗證即可。
時間複雜度O((n2)log n) 其實這個log n相當的小,1000ms的時限是可以過完50分的數據的。
下面給出50分的暴力代碼:

#include<cstdio>  
#define MAXN 101000  
int gcd(int a,int b)  
{  
    int t;  
    while(b>0)  
    {  
        t=a;  
        a=b;  
        b=t%b;  
    }  
    return a;  
}//非遞歸輾轉相除法;  
bool P[MAXN];  
int n;  
bool ok(int x)  
{  
    for(int i=2;i*i<=x;i++)  
    {  
        if(x%i==0)  
        {  
            if(!P[i])  
            {  
                return false;  
            }  
        }  
        while(x%i==0)  
        {  
            x/=i;  
        }  
    }  
    if(x>1&&P[x]==0)//如果x仍大於1,說明x自身是個質數,以至於在x^0.5範圍內找不到x的因數;  
    {  
        return false;  
    }  
    return true;  
}//判斷是否爲子集  
int main()  
{  
    freopen("prime.in","r",stdin);  
    freopen("prime.out","w",stdout);   
    scanf("%d",&n);  
    int k=n;  
    for(int i=2;i*i<=n;i++)  
    {  
        if(k%i==0)  
        {  
            P[i]=1;  
        }  
        while(k%i==0)  
        {  
            k/=i;  
        }  
    }//分解n;  
    if(k>1)  
    {  
        P[k]=1;  
    } //原理見ok函數  
    long long cnt=0LL;  
    for(int i=1;i<=n;i++)  
    {  
        for(int j=i;j<=n;j++)  
        {  
            if(ok(gcd(i,j)))  
            {  
                cnt+=1LL;  
            }  
        }  
    }  
    printf("%I64d",cnt);  
    return 0;  
}  

100分算法:
可以看出數據範圍是相當的大,枚舉a,b是不可行的。我們不妨思考一下一個比較基礎的問題:
如何求解1~n中有多少個數與n互質?答案是用歐拉函數。而歐拉函數的原理即是將n含有的質因數逐一篩去。
因爲我們要求解的數是一定不含這些質因數的,所以將這些質數篩去是合理的。
而現在的問題是,我們要求解有多少個小於i的數,與i的公共質因數落在一個確定的集合中。
在求歐拉函數時,由於不能有質數,所以我們篩去質數;而這裏,P_n 中的質數是可以出現的,於是我們就沒有必要將它們篩去。也就是說,計算phi[i]=i(11p1)(11p2)(11pk) 時,凡是在集合P_n中的質數,就不乘以(11p) ,只將那些不在集合Pi 中的質數篩掉即可。
甚至,我們可以有一個更大膽的想法:對於P_n中的質數,我們並不將其視爲質數。這樣題設條件就是一種變相的互質了!!!
於是當我們求出做了手腳的歐拉函數phi[i]後,ni=1phi[i] 就是最終答案!
phi[i]的求法,最優秀的是用線性篩,但是沒有這個必要。標程採用的O(n*log n)的算法,跑得飛快!
爲了方便,標程的做法是並不在求歐拉函數時做判斷,而是照常求出歐拉函數,但在求和時,將原本不該篩而現在多篩了的部分補償回來。
下面放100分代碼:老實說,比暴力要短。

#include<cstdio>  
#define MAXN 2000100  
int P[MAXN],tot=0;  
int n,f[MAXN];  
int main()  
{  
    freopen("prime.in","r",stdin);  
    freopen("prime.out","w",stdout);  
    scanf("%d",&n);  
    int k=n;  
    for(int i=2;i*i<=k;i++)  
    {  
        if(k%i==0)  
        {  
            P[++tot]=i;  
        }  
        while(k%i==0)  
        {  
            k/=i;  
        }  
    }  
    if(k>1)  
    {  
        P[++tot]=k;  
    }  
    for(int i=1;i<=n;i++)  
    {  
        f[i]=i;  
    }  
    for(int i=2,j;i<=n;i++)  
    {  
        if(f[i]==i)  
        {  
            for(j=i;j<=n;j+=i)  
            {  
                f[j]-=f[j]/i;  
            }  
        }  
    }//O(n*log n)  
    long long ans=0;  
    for(int i=1;i<=n;i++)  
    {  
        for(int p=1;p<=tot;p++)  
        {  
            if(i%P[p]==0)  
            {  
                f[i]+=f[i]/(P[p]-1);  
            }  
        }//tot不會超過7,所以效率相當高!  
        ans+=(long long)f[i];  
    }  
    printf("%I64d",ans);  
    fclose(stdin);  
    fclose(stdout);  
    return 0;  
}  

此題還有其他100分解法,這裏簡要提一下:(下面用gcd(a,b)表示a,b的最大公約數)
對於gcd(a,b)=t,有gcd(a/t,b/t)=1,於是(a,b)的數目可以藉助phi[b/t]來求解。但是要預處理出所有合題意的t.
此解法比標程解法要麻煩許多!!!
【總結】
此題的關鍵是要對歐拉函數的原理和本質有清楚的認識,這樣纔可能用改造後的歐拉函數來求解問題。


以上是本人NOIP前發的題解,現在看來相當的naive.
之前提到線性篩歐拉函數,但是後面的補償部分仍然不是線性的。或者說是一個有常數的線性算法,常數取決於n的質因數集合的size.
其實這樣的算法效率上已經很接近線性了,不過這裏我提一下正經的線性做法:
考慮改造後的歐拉函數,記作g(x).它仍然是一個積性函數.於是就可以用線性篩直接處理出g(x).
具體來說,先用O(n) 的算法分解n的質因數,(當然你會用O(n14) 的算法更好),然後線性篩.
凡是遇到質數x,如果在P_n集合中,g(x)=x;否則,g(x)=x-1;對於合數,利用積性函數的性質求解就好.
還是放一段代碼吧:

#include<cstdio>  
#define MAXN 2000100  
int P[MAXN],tot=0;  
int n,f[MAXN];  
int main()  
{  
    freopen("prime.in","r",stdin);  
    freopen("prime.out","w",stdout);  
    scanf("%d",&n);  
    int k=n;  
    for(int i=2;i*i<=k;i++)  
    {  
        if(k%i==0)  
        {  
            P[++tot]=i;  
        }  
        while(k%i==0)  
        {  
            k/=i;  
        }  
    }  
    if(k>1)  
    {  
        P[++tot]=k;  
    }  
    for(int i=1;i<=n;i++)  
    {  
        f[i]=i;  
    }  
    for(int i=2,j;i<=n;i++)  
    {  
        if(f[i]==i)  
        {  
            for(j=i;j<=n;j+=i)  
            {  
                f[j]-=f[j]/i;  
            }  
        }  
    }//O(n*log n)  
    long long ans=0;  
    for(int i=1;i<=n;i++)  
    {  
        for(int p=1;p<=tot;p++)  
        {  
            if(i%P[p]==0)  
            {  
                f[i]+=f[i]/(P[p]-1);  
            }  
        }//tot不會超過7,所以效率相當高!  
        ans+=(long long)f[i];  
    }  
    printf("%I64d",ans);  
    fclose(stdin);  
    fclose(stdout);  
    return 0;  
}  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章