自創題目詳解
數據包
【數據資源】鏈接:http://pan.baidu.com/s/1eRHCMwq 密碼:9laz
題面
【題目名稱】分解質因數
【時間限制】1000ms
【空間限制】128M
【題目描述】
【輸入格式】
一個正整數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分算法:
只要聽話,就一定可以拿全50分!我們只需要先將n分解質因數,並將這些質因數都記錄下來。
之後老老實實枚舉a,b再用歐幾里得求出最大公約數,直接分解質因數驗證即可。
時間複雜度
下面給出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 中的質數是可以出現的,於是我們就沒有必要將它們篩去。也就是說,計算
甚至,我們可以有一個更大膽的想法:對於P_n中的質數,我們並不將其視爲質數。這樣題設條件就是一種變相的互質了!!!
於是當我們求出做了手腳的歐拉函數phi[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).
具體來說,先用
凡是遇到質數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;
}