因子和与因子个数
摘抄于《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; }