摘抄於《ACM-ICPC 程序設計系列數論及應用》
基本理論
定義1:因子和函數σ定義為整數n的所有正因子之和,記為σ(n).
定義2:因子個數函數τ定義為正整數n的所有正因子個數,記為τ(n).
定理
定理1:如果f是積性函數F(n),那么f的和函數F(n)=Σ(d|n) f(d) 也是積性函數
推論:因子和函數σ與因子個數函數τ是積性函數(只要令 f(n)=n 和f(n)=1即可)
定理2:設p是一個素數,a是一個正整數,那么
σ(pa)=1+p+p2+p3+..+pa= (pa+1-1)/(p-1) (等比數列求和公式可得)
τ(pa)=a+1; (pa的因子為 1 ,p,p2,p3,...pa)
定理3:設正整數n有素因子分解n=p1a1*p2a2*.....psas (唯一分解定理)
σ(n)= (p1a1+1-1)/(p1-1) * (p2a2+1-1)/(p2-1) *(psas+1-1)/(ps-1) = Π( j=1,s) (pjaj+1-1)/(pj-1) (由定理2得)
τ(n)=(a1+1)*(a2+1)*...*(as+1)= Π( j=1,s) (aj+1)
代碼實現:
因為求因子和函數與因子個數函數同求歐拉函數值一樣,都需要素因子分解。代碼寫法寫法大致一樣。
求因子和:
int sum(int n) { int s=1; for(int i=2;i*i<=n;i++) { if(n%i==0) { int a=1; while(n%i==0) { n/=i; a*=i; } s=s*(a*i-1)/(i-1); } } if(n>1) s=s*(1+n); return s; }
求因子個數:
ll count(ll n) { ll s=1; for(ll i=2;i*i<=n;i++) { if(n%i==0) { ll num=0; while(n%i==0) { n/=i; num++; } s=s*(num+1); } } if(n>1) s=s*2; return s; }
實戰:
poj 2992(因子個數):求出C(k,n)因子個數 (0<=k<=n<=431)
思路:
如果直接求組合數是不可能的因為,其太大了。
要求因數個數,以上可知,要先求素因子,所以要素因子分解,組合數可以寫成階乘的形式,那可以階乘素因子分解,求階乘對於每個素因子可分解的個數。
步驟:
1.先篩出431內的素數。
2.對每個階乘進行素因子分解
3.根據上面的定理求因子個數
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #define inf 0x3f3f3f3f using namespace std; typedef long long ll; bool isprime[450]; int nprime; int prime[100]; void init()//篩出431內所有的素數 { nprime=0; memset(isprime,1,sizeof(isprime)); isprime[0]=isprime[1]=0; for(int i=2;i<450;i++) { if(isprime[i]) { nprime++; prime[nprime]=i; for(int j=2*i;j<450;j+=i) isprime[j]=0; } } } int main() { init(); int jie[450][100];//jie[i][j]表示i階乘分解成第j個素數有幾個 memset(jie,0,sizeof(jie)); for(int i=1;i<=nprime;i++) { for(int j=2;j<450;j++)//求出每個階乘對於每個素數的個數 jie[j][i]=j/prime[i]+jie[j/prime[i]][i]; } ll c[450][450]; for(int i=2;i<450;i++) { for(int j=1;j<i;j++) { c[i][j]=1; for(int k=1;k<=nprime&&jie[i][k];k++) { int s=jie[i][k]-jie[j][k]-jie[i-j][k];//C(i,j)= i!/(j!*(i-j)!) if(s) c[i][j]*=(s+1);//求因子個數 } } } int n,k; while(scanf("%d%d",&n,&k)!=EOF) { if(!k||k==n) printf("1\n"); else printf("%lld\n",c[n][k]); } return 0; }
poj 1845(求因子和):求 AB%9901,(0<=A,B<=50000000)
思路:
對於A素因子分解為A=p1k1*p2k2*...*pmkm,那么AB=p1k1B*p2k2B*...*pmkmB
因為積性函數的性質,S=(1+p1+p12+...+p1k1B) * (1+p2+p22+..+p2k2B)*...*(1+pm+pm2+..+pmkmB)
由等比數列求和得:S=(p1k1B+1-1)/(p1-1) * (p2k2B+1-1)/(p2-1) *...* (pmkmB+1-1)/(pm-1)
依然要素因子分解,這里用二維數組su[][]用於存素因子和其個數,su[i][0]表示第i個素因子為多少,su[i][1]表示其個數。
再根據推導公式求即可,但是除數(p-1)是不能取模的,要變成乘以逆元,因為9901是素數,直接用費馬小定理即可
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #define inf 0x3f3f3f3f using namespace std; typedef long long ll; ll mul(ll x,ll y)//快速冪 { ll ans=1; while(y) { if(y&1) ans=(ans*x)%9901; x=(x*x)%9901; y>>=1; } return ans; } int main() { ll a,b; ll su[101][2]; while(scanf("%lld%lld",&a,&b)!=EOF) { if(a==0) printf("0\n"); else if(a==1||b==0) printf("1\n"); else { ll p=0; for(int i=2;i*i<=a;i++)//素因子分解,求個數 { if(a%i==0) { su[p][0]=i; su[p][1]=0; while(a%i==0) { a/=i; su[p][1]++; } p++; } } if(a>1) { su[p][0]=a; su[p][1]=1; p++; } for(int i=0;i<p;i++)//記得素因子個數是 k*B+1 { su[i][1]*=b; su[i][1]++; } ll m=1; ll x,y; for(int i=0;i<p;i++) { if(su[i][0]%9901==0) continue; if(su[i][0]%9901==1) { m=m*su[i][1]%9901; continue; } m=m*(mul(su[i][0],su[i][1])-1)%9901;//p^(k*B+1)-1 x=mul(su[i][0]-1,9899)%9901;//費馬小定理,求(p-1)的逆元 x=(x%9901+9901)%9901;//一項素因子的值 m=m*x%9901; } printf("%lld\n",m); } } return 0; }